- Add comprehensive Training Leads system for HVAC trainers * New /trainer/training-leads/ page with tabular contact submission display * HVAC_Training_Leads class with AJAX status updates and filtering * Empty state messaging and profile sharing CTA * Database integration with existing contact forms system - Restructure trainer navigation menu for better UX * Rename "Customize" to "Profile" with logical groupings * Move "Logout" under "Profile" submenu * Change "Personal Profile" to "Trainer Profile" * Add "Training Leads" under Profile section * Update help menu to show only question mark icon positioned far right - Enhance documentation system * Fix /trainer/documentation/ page styling and navigation integration * Update content to reflect current platform features * Add Training Leads documentation and navigation guide * Implement proper WordPress template structure - Update user management * Change joe@upskillhvac.com display name to "Joe Medosch" * Assign Joe as author of measureQuick headquarters venue * Assign Joe as author of measureQuick and Upskill HVAC organizers 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1059 lines
No EOL
27 KiB
Markdown
1059 lines
No EOL
27 KiB
Markdown
# 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 <repository-url>
|
|
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 '<div class="hvac-read-only-field">' . esc_html($certification_status) . '</div>';
|
|
}
|
|
```
|
|
|
|
### 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 '<input type="hidden" name="certification_type" value="' . esc_attr($certification_type) . '" />';
|
|
echo '<div class="hvac-read-only-field">' . esc_html($certification_type) . '</div>';
|
|
} else if ( current_user_can('administrator') || current_user_can('hvac_master_trainer') ) {
|
|
// Admin/Master Trainer: full edit access
|
|
echo '<select name="certification_type">';
|
|
// ... render editable dropdown
|
|
echo '</select>';
|
|
}
|
|
```
|
|
|
|
### 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(
|
|
'<div class="hvac-share-card-loading">' +
|
|
'<span class="dashicons dashicons-update spin"></span>' +
|
|
'<p>Loading profile card...</p>' +
|
|
'</div>'
|
|
);
|
|
|
|
// Error states with helpful messaging
|
|
ProfileSharing.showError = function(message) {
|
|
$cardContainer.html(
|
|
'<div class="hvac-share-card-loading">' +
|
|
'<span class="dashicons dashicons-warning" style="color: #dc3545;"></span>' +
|
|
'<p style="color: #dc3545;">' + message + '</p>' +
|
|
'</div>'
|
|
);
|
|
};
|
|
|
|
// 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
|
|
<button type="button" class="hvac-button hvac-button-secondary hvac-share-profile-btn"
|
|
data-profile-id="<?php echo esc_attr($profile->ID); ?>">
|
|
<span class="dashicons dashicons-share"></span> Share Profile
|
|
</button>
|
|
|
|
// Include the modal HTML at the end of the template
|
|
<div id="hvac-share-profile-modal" class="hvac-share-modal" style="display: none;">
|
|
<!-- Modal content -->
|
|
</div>
|
|
```
|
|
|
|
#### 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/) |