# HVAC Plugin Development Guide ## Table of Contents - [Development Setup](#development-setup) - [Coding Standards](#coding-standards) - [Architecture Principles](#architecture-principles) - [Development Workflow](#development-workflow) - [Testing Guidelines](#testing-guidelines) - [Deployment Process](#deployment-process) - [Security Best Practices](#security-best-practices) - [Performance Optimization](#performance-optimization) ## Development Setup ### Prerequisites - PHP 7.4+ (8.0+ recommended) - Node.js 16+ and npm - Composer - WP-CLI - Git ### Local Environment Setup 1. **Clone Repository** ```bash git clone cd upskill-event-manager ``` 2. **Install Dependencies** ```bash # PHP dependencies composer install # Node dependencies npm install # Create .env file cp .env.example .env # Edit .env with your environment details ``` 3. **Configure WordPress** - Install WordPress locally - Create database - Configure wp-config.php - Install The Events Calendar plugin 4. **Activate Plugin** ```bash wp plugin activate hvac-community-events ``` ## Coding Standards ### PHP Standards 1. **Follow WordPress Coding Standards** ```php // Good function hvac_get_trainer_events( $trainer_id ) { $args = array( 'post_type' => 'tribe_events', 'posts_per_page' => -1, 'meta_query' => array( array( 'key' => '_EventTrainerID', 'value' => $trainer_id, 'compare' => '=' ) ) ); return get_posts( $args ); } // Bad function getTrainerEvents($trainerId) { $args = [ 'post_type' => 'tribe_events', 'posts_per_page' => -1, 'meta_query' => [ ['key' => '_EventTrainerID', 'value' => $trainerId, 'compare' => '='] ] ]; return get_posts($args); } ``` 2. **Namespace Usage** ```php // Use prefixes instead of PHP namespaces for WordPress compatibility class HVAC_Event_Manager { // Class implementation } ``` 3. **Security First** ```php // Always escape output echo esc_html( $trainer_name ); echo esc_url( $profile_url ); echo esc_attr( $css_class ); // Sanitize input $trainer_id = absint( $_GET['trainer_id'] ); $search = sanitize_text_field( $_POST['search'] ); // Verify nonces if ( ! wp_verify_nonce( $_POST['hvac_nonce'], 'hvac_action' ) ) { wp_die( 'Security check failed' ); } ``` ### JavaScript Standards 1. **jQuery Usage** ```javascript // Always use jQuery in no-conflict mode jQuery(document).ready(function($) { // $ is now safe to use $('.hvac-button').on('click', function(e) { e.preventDefault(); // Handle click }); }); ``` 2. **AJAX Requests** ```javascript jQuery.ajax({ url: hvac_ajax.ajax_url, type: 'POST', data: { action: 'hvac_update_profile', nonce: hvac_ajax.nonce, data: formData }, success: function(response) { if (response.success) { // Handle success } else { // Handle error console.error(response.data.message); } } }); ``` ### CSS Standards 1. **BEM Methodology** ```css /* Block */ .hvac-trainer-card {} /* Element */ .hvac-trainer-card__header {} .hvac-trainer-card__content {} /* Modifier */ .hvac-trainer-card--featured {} ``` 2. **Specificity Rules** ```css /* Avoid overly specific selectors */ /* Bad */ body.logged-in .hvac-wrapper div.container .hvac-trainer-card {} /* Good */ .hvac-trainer-card {} /* When specificity is needed for theme overrides */ .hvac-astra-integrated .hvac-trainer-card {} ``` ## Architecture Principles ### 1. Single Responsibility Principle Each class should have one primary responsibility: ```php // Good - Focused classes class HVAC_Certificate_Generator {} // Only handles PDF generation class HVAC_Event_Manager {} // Only manages events class HVAC_Email_Sender {} // Only sends emails // Bad - Kitchen sink class class HVAC_Everything {} // Does certificates, events, emails, etc. ``` ### 2. Dependency Injection ```php class HVAC_Certificate_Service { private $generator; private $emailer; public function __construct( $generator, $emailer ) { $this->generator = $generator; $this->emailer = $emailer; } } ``` ### 3. Hook-Driven Architecture ```php // Use WordPress hooks for extensibility do_action( 'hvac_before_event_save', $event_data ); $event_data = apply_filters( 'hvac_event_data', $event_data ); do_action( 'hvac_after_event_save', $event_id ); ``` ### 4. Singleton Pattern (where appropriate) ```php class HVAC_Plugin { private static $instance = null; public static function instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } private function __construct() { // Initialize } } ``` ## Development Workflow ### 1. Feature Development ```bash # Create feature branch git checkout -b feature/new-trainer-dashboard # Make changes # Test thoroughly # Commit with meaningful messages git add . git commit -m "feat: Add advanced filtering to trainer dashboard - Add date range picker - Add event status filter - Add export functionality" # Push and create PR git push origin feature/new-trainer-dashboard ``` ### 2. Git Commit Messages Follow conventional commits: - `feat:` New feature - `fix:` Bug fix - `docs:` Documentation - `style:` Formatting changes - `refactor:` Code restructuring - `test:` Test additions/changes - `chore:` Maintenance tasks ### 3. Code Review Checklist - [ ] Follows WordPress coding standards - [ ] Security measures implemented (escaping, sanitization) - [ ] Performance impact considered - [ ] Backward compatibility maintained - [ ] Documentation updated - [ ] Tests written/updated ## Testing Guidelines ### 1. Unit Testing ```php class Test_HVAC_Event_Manager extends WP_UnitTestCase { public function test_create_event() { $event_data = array( 'post_title' => 'Test Event', 'EventStartDate' => '2025-08-15 09:00:00' ); $event_id = HVAC_Event_Manager::create_event( $event_data ); $this->assertIsInt( $event_id ); $this->assertGreaterThan( 0, $event_id ); } } ``` ### 2. E2E Testing with Playwright ```javascript test('Trainer can create event', async ({ page }) => { // Login await loginAsTrainer(page); // Navigate to event creation await page.goto('/trainer/event/manage/'); // Fill form await page.fill('#event-title', 'HVAC Training Session'); await page.selectOption('#event-venue', 'Main Campus'); // Submit await page.click('button[type="submit"]'); // Verify await expect(page).toHaveURL(/event-created/); }); ``` ### 3. Manual Testing Checklist - [ ] Test on staging before production - [ ] Test with different user roles - [ ] Test on mobile devices - [ ] Test with different themes (especially Astra) - [ ] Test with caching enabled ## Deployment Process ### 1. Pre-Deployment Checks ```bash # Run validation bin/pre-deployment-check.sh # Run tests npm test phpunit ``` ### 2. Staging Deployment ```bash # Deploy to staging scripts/deploy.sh staging # Verify deployment scripts/verify-plugin-fixes.sh ``` ### 3. Production Deployment ```bash # Only when explicitly requested scripts/deploy.sh production # Requires double confirmation ``` ### 4. Post-Deployment - Monitor error logs - Check critical functionality - Be ready to rollback if needed ## Security Best Practices ### 1. Data Validation ```php // Input validation if ( ! is_email( $email ) ) { wp_die( 'Invalid email address' ); } // Type checking $event_id = absint( $_POST['event_id'] ); if ( ! $event_id ) { wp_die( 'Invalid event ID' ); } ``` ### 2. SQL Queries ```php // Always use prepared statements global $wpdb; $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE post_author = %d AND post_type = %s", $user_id, 'tribe_events' ) ); ``` ### 3. File Uploads ```php // Validate file types $allowed_types = array( 'jpg', 'jpeg', 'png', 'pdf' ); $file_type = wp_check_filetype( $file['name'] ); if ( ! in_array( $file_type['ext'], $allowed_types ) ) { wp_die( 'Invalid file type' ); } ``` ### 4. Capability Checks ```php // Always verify user permissions if ( ! current_user_can( 'manage_own_events' ) ) { wp_die( 'Insufficient permissions' ); } ``` ## Performance Optimization ### 1. Database Queries ```php // Cache expensive queries $cache_key = 'hvac_trainer_events_' . $trainer_id; $events = wp_cache_get( $cache_key ); if ( false === $events ) { $events = get_posts( $args ); wp_cache_set( $cache_key, $events, '', 3600 ); // 1 hour } ``` ### 2. Asset Loading ```php // Load assets only where needed public function enqueue_scripts() { if ( ! $this->is_plugin_page() ) { return; } wp_enqueue_script( 'hvac-dashboard' ); } ``` ### 3. AJAX Optimization ```javascript // Debounce search requests let searchTimeout; $('#search').on('keyup', function() { clearTimeout(searchTimeout); searchTimeout = setTimeout(function() { performSearch(); }, 300); }); ``` ### 4. Image Optimization - Use appropriate image sizes - Lazy load images - Compress before upload - Use WebP format when possible ## Common Patterns ### 1. AJAX Handler Pattern ```php class HVAC_Ajax_Handler { public function __construct() { add_action( 'wp_ajax_hvac_action', array( $this, 'handle_request' ) ); } public function handle_request() { // Verify nonce if ( ! wp_verify_nonce( $_POST['nonce'], 'hvac_nonce' ) ) { wp_send_json_error( 'Invalid nonce' ); } // Check capabilities if ( ! current_user_can( 'required_capability' ) ) { wp_send_json_error( 'Insufficient permissions' ); } // Process request $result = $this->process_data( $_POST['data'] ); // Return response if ( $result ) { wp_send_json_success( $result ); } else { wp_send_json_error( 'Processing failed' ); } } } ``` ### 2. Settings Page Pattern ```php class HVAC_Settings { public function __construct() { add_action( 'admin_menu', array( $this, 'add_menu' ) ); add_action( 'admin_init', array( $this, 'register_settings' ) ); } public function add_menu() { add_options_page( 'HVAC Settings', 'HVAC Events', 'manage_options', 'hvac-settings', array( $this, 'render_page' ) ); } public function register_settings() { register_setting( 'hvac_settings', 'hvac_options' ); } } ``` ## Access Control Patterns ### Role-Based Field Access The plugin implements sophisticated role-based access control for sensitive data: ```php // Check permissions for certification field editing $can_edit_certifications = current_user_can('administrator') || current_user_can('hvac_master_trainer'); if ( $can_edit_certifications ) { // Allow editing certification fields update_user_meta($user_id, 'certification_status', sanitize_text_field($_POST['certification_status'])); } else { // Show read-only display for regular trainers echo '
' . esc_html($certification_status) . '
'; } ``` ### Field-Level Access Control Implementation ```php // Different access levels for different user types if ( current_user_can('hvac_trainer') && ! current_user_can('hvac_master_trainer') ) { // Trainer: read-only access to certification fields echo ''; echo '
' . esc_html($certification_type) . '
'; } else if ( current_user_can('administrator') || current_user_can('hvac_master_trainer') ) { // Admin/Master Trainer: full edit access echo ''; } ``` ### Access Control Best Practices 1. **Always validate permissions server-side** - Never rely on frontend-only restrictions 2. **Use capability checks** - Check specific capabilities rather than user roles when possible 3. **Implement graceful degradation** - Show read-only versions rather than hiding fields entirely 4. **Visual indicators** - Clearly indicate when fields are read-only 5. **Consistent patterns** - Use the same access control patterns throughout the plugin ## CSV Import System ### Enhanced CSV Import Architecture The plugin includes a comprehensive CSV import system designed to process trainer profile data with full taxonomy integration. #### File Structure ``` includes/ ├── enhanced-csv-import-from-file.php # Main CSV import class ├── class-hvac-geocoding-ajax.php # AJAX handlers (updated) └── taxonomy-migration.php # Taxonomy migration utilities ``` #### CSV Import Class ```php class HVAC_Enhanced_CSV_Import { /** * Execute comprehensive CSV import * @return array Import results and statistics */ public function execute_import() /** * Process individual CSV row * @param array $headers CSV headers * @param array $row CSV row data * @param int $row_number Row number for error reporting */ private function process_row($headers, $row, $row_number) /** * Create or update trainer profile * @param int $user_id WordPress user ID * @param array $data CSV row data * @return int Profile post ID */ private function create_or_update_profile($user_id, $data) /** * Assign taxonomy terms to profile * @param int $profile_id Profile post ID * @param array $data CSV row data */ private function assign_taxonomies($profile_id, $data) } ``` #### Supported CSV Fields The import system processes 19 CSV fields: **Contact Information:** - `Name`, `Last Name`, `Email`, `Phone Number` **Professional Data:** - `Company Name`, `Role`, `Company Website` **Location Data:** - `Country`, `State`, `City` **Certification Information:** - `Date Certified`, `Certification Type`, `Certification Status` **Taxonomy Fields:** - `Business Type` → `business_type` taxonomy - `Training Audience` → `training_audience` taxonomy **System Fields:** - `User ID`, `Application Details`, `Create Venue`, `Create Organizer` #### Multi-Value Taxonomy Handling The system handles comma-separated taxonomy values: ```php // Input: "Educator,Consultant" // Output: Two taxonomy terms assigned to profile private function parse_comma_separated($value) { $items = explode(',', $value); return array_map('trim', $items); } private function assign_taxonomy_terms($profile_id, $taxonomy, $terms) { foreach ($terms as $term_name) { // Get or create term $term = get_term_by('name', $term_name, $taxonomy); if (!$term) { $term_data = wp_insert_term($term_name, $taxonomy); } } wp_set_object_terms($profile_id, $term_ids, $taxonomy); } ``` #### AJAX Integration The CSV import integrates with the existing AJAX system: ```javascript // Execute enhanced CSV import jQuery.post(hvac_ajax.ajax_url, { action: 'hvac_run_enhanced_import', nonce: hvac_ajax.nonce }, function(response) { if (response.success) { console.log('Import completed:', response.data); // Display results to user } }); ``` #### Error Handling and Logging ```php try { $results = $this->process_row($headers, $row, $row_number); } catch (Exception $e) { $this->results['errors']++; $this->results['details'][] = "Row $row_number error: " . $e->getMessage(); error_log("CSV Import Row $row_number Error: " . $e->getMessage()); } ``` #### Testing the CSV Import ```bash # Test import locally php test-enhanced-csv-import.php # Test on staging ssh user@staging-server "cd /path/to/plugin && php test-csv-import.php" ``` ### Development Best Practices for CSV Import 1. **Always validate CSV structure** before processing 2. **Handle missing/malformed data gracefully** 3. **Use transactions for large imports** when possible 4. **Provide detailed progress feedback** to users 5. **Log all import activities** for debugging 6. **Test with various CSV formats and edge cases** ## Trainer Profile Sharing Implementation ### Overview The trainer profile sharing system allows trainers to generate shareable URLs and QR codes for their profiles, enabling professional marketing and networking opportunities. ### Core Components #### 1. HVAC_QR_Generator Class The main class handling QR code generation and profile sharing functionality: ```php class HVAC_QR_Generator { private static $instance = null; public static function instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->init_hooks(); } private function init_hooks() { // AJAX handlers add_action('wp_ajax_hvac_get_profile_share_data', [$this, 'ajax_get_profile_share_data']); add_action('wp_ajax_nopriv_hvac_get_profile_share_data', [$this, 'ajax_get_profile_share_data']); // URL rewrite rules add_action('init', [$this, 'add_profile_rewrite_rules']); add_filter('query_vars', [$this, 'add_profile_query_vars']); } } ``` #### 2. QR Code Generation The system uses QR Server API for generating QR codes (switched from deprecated Google Charts API): ```php public function generate_qr_url($data, $size = 200, $error_correction = 'M') { $encoded_data = urlencode($data); $qr_url = sprintf( 'https://api.qrserver.com/v1/create-qr-code/?size=%dx%d&data=%s&ecc=%s', $size, $size, $encoded_data, strtoupper($error_correction) ); return $qr_url; } ``` **Key Implementation Notes:** - **API Migration**: Switched from Google Charts API (deprecated) to QR Server API - **URL Encoding**: Proper URL encoding prevents malformed QR codes - **Error Correction**: Medium level (M) provides good balance of data capacity and error recovery - **Size Flexibility**: Configurable size for different use cases #### 3. Share URL Structure Profile sharing URLs follow a hierarchical pattern with proper WordPress rewrite rules: ```php public function add_profile_rewrite_rules() { add_rewrite_rule( '^find-a-trainer/profile/([0-9]+)/?$', 'index.php?pagename=find-a-trainer&trainer_profile_id=$matches[1]', 'top' ); } public function get_trainer_profile_share_url($profile_id) { $find_trainer_page = get_page_by_path('find-a-trainer'); if (!$find_trainer_page) { return false; } $base_url = get_permalink($find_trainer_page->ID); // CRITICAL: Include trailing slash for WordPress rewrite rules $profile_url = trailingslashit($base_url) . 'profile/' . $profile_id . '/'; return $profile_url; } ``` **Important Implementation Details:** - **Trailing Slash Required**: WordPress rewrite rules require trailing slash - **Query Variables**: Custom query var `trainer_profile_id` for profile identification - **URL Pattern**: `/find-a-trainer/profile/{profile_id}/` structure #### 4. AJAX Handler Implementation The AJAX handler follows security best practices with nonce verification: ```php public function ajax_get_profile_share_data() { // Security: Verify nonce if (!wp_verify_nonce($_POST['nonce'], 'hvac_profile_sharing')) { wp_send_json_error(['message' => 'Security check failed']); return; } $profile_id = intval($_POST['profile_id']); if (!$profile_id) { wp_send_json_error(['message' => 'Invalid profile ID']); return; } // Get comprehensive share data $share_data = $this->get_trainer_share_data($profile_id); if (!$share_data) { wp_send_json_error(['message' => 'Profile not found or not accessible']); return; } wp_send_json_success($share_data); } ``` #### 5. Frontend JavaScript Implementation The JavaScript component handles the share modal and user interactions: ```javascript var ProfileSharing = { init: function() { this.bindEvents(); }, bindEvents: function() { $(document).on('click', '.hvac-share-profile-btn', this.openShareModal); $(document).on('click', '.hvac-copy-url-btn', this.copyShareUrl); $(document).on('click', '.hvac-modal-close', this.closeShareModal); // Escape key support $(document).on('keydown', function(e) { if (e.keyCode === 27 && $('.hvac-share-modal:visible').length) { ProfileSharing.closeShareModal(); } }); }, loadShareData: function(profileId) { $.ajax({ url: hvac_sharing.ajax_url, type: 'POST', data: { action: 'hvac_get_profile_share_data', profile_id: profileId, nonce: hvac_sharing.nonce }, success: function(response) { if (response.success && response.data) { ProfileSharing.populateShareData(response.data); } else { ProfileSharing.showError(response.data ? response.data.message : 'Unknown error occurred'); } }, error: function(xhr, status, error) { ProfileSharing.showError('Network error occurred. Please try again.'); } }); } }; ``` ### Development Guidelines for Profile Sharing #### 1. Security Considerations ```php // Always verify nonces in AJAX handlers if (!wp_verify_nonce($_POST['nonce'], 'hvac_profile_sharing')) { wp_send_json_error(['message' => 'Security check failed']); return; } // Sanitize and validate input $profile_id = intval($_POST['profile_id']); if (!$profile_id || $profile_id <= 0) { wp_send_json_error(['message' => 'Invalid profile ID']); return; } // Check profile accessibility $profile = get_post($profile_id); if (!$profile || $profile->post_type !== 'trainer_profile') { wp_send_json_error(['message' => 'Profile not found']); return; } ``` #### 2. Error Handling Patterns ```php // Graceful degradation for missing data $trainer_name = get_post_meta($profile_id, 'trainer_display_name', true) ?: $user->display_name ?: 'Unknown Trainer'; // API fallback handling $qr_url = $this->generate_qr_url($profile_url, $size); if (!$qr_url) { error_log("QR code generation failed for profile {$profile_id}"); // Continue without QR code rather than failing completely } ``` #### 3. Frontend User Experience ```javascript // Loading states $cardContainer.html( '
' + '' + '

Loading profile card...

' + '
' ); // Error states with helpful messaging ProfileSharing.showError = function(message) { $cardContainer.html( '
' + '' + '

' + message + '

' + '
' ); }; // Success feedback for copy operations ProfileSharing.showCopySuccess = function($button) { var originalText = $button.text(); $button.addClass('copied').text('Copied!'); setTimeout(function() { $button.removeClass('copied').text(originalText); }, 2000); }; ``` #### 4. Template Integration ```php // In page templates, add the share button // Include the modal HTML at the end of the template ``` #### 5. Asset Management ```php // In HVAC_Scripts_Styles class public function localize_sharing_data($additional_data = []) { wp_localize_script('hvac-profile-sharing', 'hvac_sharing', array_merge([ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('hvac_profile_sharing'), 'strings' => [ 'copied' => 'Copied!', 'copy_error' => 'Copy failed' ] ], $additional_data)); } ``` ### Testing Profile Sharing #### 1. Unit Testing ```php // Test QR URL generation public function test_qr_url_generation() { $qr_generator = HVAC_QR_Generator::instance(); $test_url = 'https://example.com/find-a-trainer/profile/123/'; $qr_url = $qr_generator->generate_qr_url($test_url, 200, 'M'); $this->assertStringContains('api.qrserver.com', $qr_url); $this->assertStringContains('size=200x200', $qr_url); $this->assertStringContains(urlencode($test_url), $qr_url); } // Test share URL generation public function test_share_url_generation() { $qr_generator = HVAC_QR_Generator::instance(); $profile_id = 123; $share_url = $qr_generator->get_trainer_profile_share_url($profile_id); $this->assertStringEndsWith('/', $share_url); // Must have trailing slash $this->assertStringContains('/profile/' . $profile_id . '/', $share_url); } ``` #### 2. End-to-End Testing ```javascript // Playwright test for profile sharing test('trainer can share profile with QR code', async ({ page }) => { await page.goto('/trainer/profile/'); await page.click('.hvac-share-profile-btn'); // Wait for modal to appear await page.waitForSelector('#hvac-share-profile-modal'); // Verify share URL is populated const shareUrl = await page.inputValue('#hvac-share-url'); expect(shareUrl).toContain('/find-a-trainer/profile/'); // Verify QR code image is loaded const qrImage = page.locator('.hvac-share-qr img'); await expect(qrImage).toBeVisible(); // Test copy functionality await page.click('.hvac-copy-url-btn'); await expect(page.locator('.hvac-copy-url-btn')).toHaveClass(/copied/); }); ``` ### Deployment Considerations #### 1. URL Rewrite Rules After deploying profile sharing functionality: ```bash # Flush rewrite rules to activate new URL patterns wp rewrite flush # Verify rewrite rules are working wp rewrite list | grep "find-a-trainer" ``` #### 2. QR Code API Dependencies - **External Dependency**: QR Server API (https://api.qrserver.com) - **Fallback**: Consider implementing fallback QR generation if API becomes unavailable - **Caching**: QR URLs are generated dynamically but could be cached for performance #### 3. Performance Monitoring ```php // Add timing monitoring for QR generation $start_time = microtime(true); $qr_url = $this->generate_qr_url($data, $size); $generation_time = microtime(true) - $start_time; if ($generation_time > 2.0) { error_log("Slow QR generation: {$generation_time}s for profile {$profile_id}"); } ``` ## Resources - [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/) - [WordPress Plugin Handbook](https://developer.wordpress.org/plugins/) - [The Events Calendar Documentation](https://theeventscalendar.com/knowledgebase/) - [WordPress Taxonomy API](https://developer.wordpress.org/reference/functions/wp_insert_term/) - [Playwright Documentation](https://playwright.dev/)