upskill-event-manager/docs/WORDPRESS-BEST-PRACTICES.md
Ben 8752905f9e docs: Comprehensive documentation update with best practices
- Created WORDPRESS-BEST-PRACTICES.md with complete WP standards guide
- Created TESTING-GUIDE.md with E2E testing procedures and display session setup
- Updated CLAUDE.md with JavaScript simplification and recent fixes
- Enhanced main docs README with new documentation links
- Added guidance on MCP Playwright usage vs standard Playwright
- Documented proper role checking (roles vs capabilities)
- Included display session configuration for headless testing
- Added production testing checklists and debug procedures

Key additions:
- How to use jQuery properly in WordPress (no compatibility layers needed)
- Display session setup (DISPLAY=:0) for Playwright testing
- Security best practices with proper examples
- Common pitfalls and solutions
- Test user accounts for staging/production

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-18 22:44:43 -03:00

10 KiB

WordPress Best Practices Guide

Overview

This guide documents WordPress best practices as implemented in the HVAC Community Events plugin. Following these practices ensures maintainability, security, and compatibility with WordPress core and other plugins.

JavaScript and jQuery

DO: Use WordPress jQuery Patterns

// CORRECT - WordPress standard pattern
jQuery(document).ready(function($) {
    'use strict';
    
    // $ is now available within this scope
    $('.my-element').addClass('active');
});

DON'T: Create Complex Compatibility Layers

// WRONG - Unnecessary complexity
(function($) {
    if (typeof $ === 'undefined' && typeof window.jQuery !== 'undefined') {
        $ = window.jQuery;
    }
    // Complex error handling for basic jQuery operations
})(jQuery);

Key Principles

  1. WordPress loads jQuery in no-conflict mode by default
  2. Use jQuery globally, $ within document ready callbacks
  3. Don't create compatibility shims - they add complexity without value
  4. Trust WordPress's jQuery implementation

Security Best Practices

Always Escape Output

// ✅ CORRECT
echo esc_html($user_input);
echo esc_url($link);
echo esc_attr($attribute);
echo wp_kses_post($content_with_html);

// ❌ WRONG
echo $user_input;

Always Sanitize Input

// ✅ CORRECT
$clean_text = sanitize_text_field($_POST['field']);
$clean_email = sanitize_email($_POST['email']);
$clean_url = esc_url_raw($_POST['url']);

// ❌ WRONG
$data = $_POST['field'];

Always Verify Nonces

// ✅ CORRECT
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'action_name')) {
    wp_die('Security check failed');
}

// ❌ WRONG - No nonce verification
if (isset($_POST['submit'])) {
    // Process form
}

Always Check Capabilities

// ✅ CORRECT
if (!current_user_can('edit_posts')) {
    wp_die('Insufficient permissions');
}

// Check custom roles properly
$user = wp_get_current_user();
if (!in_array('hvac_trainer', $user->roles)) {
    wp_die('Access denied');
}

// ❌ WRONG
if (!current_user_can('hvac_trainer')) { // Custom roles aren't capabilities!
    wp_die('Access denied');
}

Plugin Development Standards

File Organization

hvac-community-events/
├── hvac-community-events.php    # Main plugin file
├── includes/                    # PHP classes and functions
│   ├── class-*.php             # One class per file
│   └── functions.php            # Helper functions
├── assets/                      # Frontend resources
│   ├── css/                    # Stylesheets
│   ├── js/                     # JavaScript files
│   └── images/                 # Images
├── templates/                   # Page templates
└── docs/                       # Documentation

Class Structure

/**
 * HVAC Example Class
 *
 * @package HVAC_Community_Events
 * @since 1.0.0
 */
class HVAC_Example {
    
    /**
     * Instance
     * @var HVAC_Example
     */
    private static $instance = null;
    
    /**
     * Get instance (Singleton pattern)
     */
    public static function instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Constructor
     */
    private function __construct() {
        $this->init_hooks();
    }
    
    /**
     * Initialize hooks
     */
    private function init_hooks() {
        add_action('init', array($this, 'init'));
    }
}

Naming Conventions

  • PHP Classes: HVAC_Class_Name (prefix + PascalCase)
  • Functions: hvac_function_name (prefix + snake_case)
  • Hooks: hvac_hook_name (prefix + snake_case)
  • Constants: HVAC_CONSTANT_NAME (prefix + UPPER_SNAKE_CASE)
  • Database: hvac_table_name (prefix + snake_case)

Asset Management

Conditional Loading

/**
 * Only load assets on plugin pages
 */
public function enqueue_scripts() {
    // Check if we're on a plugin page
    if (!$this->is_plugin_page()) {
        return;
    }
    
    wp_enqueue_script(
        'hvac-dashboard',
        HVAC_PLUGIN_URL . 'assets/js/hvac-dashboard.js',
        array('jquery'),
        HVAC_PLUGIN_VERSION,
        true
    );
    
    // Localize script for AJAX
    wp_localize_script('hvac-dashboard', 'hvac_ajax', array(
        'ajax_url' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('hvac_ajax_nonce')
    ));
}

CSS Organization

/* Use consistent naming and organization */
.hvac-wrapper {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
}

/* Component-based styling */
.hvac-card {
    background: #fff;
    border-radius: 8px;
    padding: 20px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

/* State modifiers */
.hvac-card--active {
    border-color: var(--hvac-primary);
}

Database Operations

Use WordPress Database API

global $wpdb;

// ✅ CORRECT - Using prepared statements
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}posts WHERE post_author = %d",
        $user_id
    )
);

// ❌ WRONG - Direct query without preparation
$results = $wpdb->get_results(
    "SELECT * FROM wp_posts WHERE post_author = " . $user_id
);

Cache Expensive Queries

$cache_key = 'hvac_trainer_events_' . $user_id;
$events = wp_cache_get($cache_key);

if (false === $events) {
    // Expensive query
    $events = $this->get_trainer_events($user_id);
    
    // Cache for 1 hour
    wp_cache_set($cache_key, $events, '', HOUR_IN_SECONDS);
}

AJAX Best Practices

Proper AJAX Handler

/**
 * AJAX handler with security checks
 */
public function ajax_handler() {
    // Verify nonce
    if (!check_ajax_referer('hvac_ajax_nonce', 'nonce', false)) {
        wp_send_json_error('Security check failed');
    }
    
    // Check capabilities
    if (!current_user_can('edit_posts')) {
        wp_send_json_error('Insufficient permissions');
    }
    
    // Sanitize input
    $data = sanitize_text_field($_POST['data']);
    
    // Process request
    $result = $this->process_data($data);
    
    // Return JSON response
    if ($result) {
        wp_send_json_success($result);
    } else {
        wp_send_json_error('Processing failed');
    }
}

Template Development

WordPress Template Requirements

<?php
/**
 * Template Name: HVAC Dashboard
 * 
 * @package HVAC_Community_Events
 */

// Security check
if (!defined('ABSPATH')) {
    exit;
}

// ALWAYS include WordPress header
get_header();

// Template content
?>
<div class="hvac-wrapper">
    <!-- Your content here -->
</div>
<?php

// ALWAYS include WordPress footer
get_footer();

Template Hierarchy

templates/
├── page-trainer-dashboard.php       # Trainer dashboard
├── page-master-dashboard.php        # Master trainer dashboard
├── page-edit-event-custom.php       # Custom event editor
└── page-trainer-profile.php         # Profile page

Error Handling

Graceful Error Handling

try {
    $result = $this->risky_operation();
} catch (Exception $e) {
    // Log error for debugging
    error_log('HVAC Plugin Error: ' . $e->getMessage());
    
    // Show user-friendly message
    if (WP_DEBUG) {
        wp_die('Error: ' . esc_html($e->getMessage()));
    } else {
        wp_die('An error occurred. Please try again later.');
    }
}

Performance Optimization

Optimize Queries

// ✅ CORRECT - Single query with JOIN
$results = $wpdb->get_results("
    SELECT p.*, pm.meta_value as venue_id
    FROM {$wpdb->posts} p
    LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
    WHERE p.post_type = 'tribe_events'
    AND pm.meta_key = '_EventVenueID'
");

// ❌ WRONG - Multiple queries in loop
foreach ($events as $event) {
    $venue = get_post_meta($event->ID, '_EventVenueID', true);
    // Process...
}

Lazy Loading

// Only load heavy resources when needed
public function maybe_load_map_data() {
    if (!is_page('find-trainer')) {
        return;
    }
    
    // Load map data only on find-trainer page
    $this->load_trainer_locations();
}

Testing with Display Session

Using Headless Testing

# Set display environment for headless testing
export DISPLAY=:0
export XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.90WDB3

# Run Playwright tests
node test-trainer-features.js

Testing Best Practices

  1. Always test on staging before production
  2. Use actual user accounts for realistic testing
  3. Verify both UI and functionality
  4. Check browser console for errors
  5. Test with different user roles

Deployment Process

Pre-deployment Checklist

# 1. Run validation
bin/pre-deployment-check.sh

# 2. Test on staging
scripts/deploy.sh staging

# 3. Verify staging
node test-all-features.js

# 4. Deploy to production (only when requested)
scripts/deploy.sh production

Post-deployment Verification

# Clear caches
wp cache flush
wp rewrite flush

# Verify pages exist
wp post list --post_type=page --name=dashboard

# Check plugin activation
wp plugin list --name=hvac-community-events

Common Pitfalls to Avoid

DON'T

  • Create standalone fixes outside plugin deployment
  • Use hardcoded database prefixes
  • Skip nonce verification
  • Echo unsanitized output
  • Assume jQuery methods exist without proper setup
  • Check capabilities using role names
  • Load assets globally
  • Use PHP namespaces (WordPress compatibility)

DO

  • Deploy complete plugin updates
  • Use $wpdb->prefix
  • Always verify nonces
  • Escape all output
  • Use WordPress jQuery patterns
  • Check roles properly with in_array()
  • Load assets conditionally
  • Use prefixed class/function names

Debugging Tips

Enable Debug Mode (Development Only)

// wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
define('SCRIPT_DEBUG', true);

Debug Logging

// Log to debug.log
error_log('HVAC Debug: ' . print_r($data, true));

// Conditional logging
if (defined('WP_DEBUG') && WP_DEBUG) {
    error_log('HVAC: Processing user ' . $user_id);
}

Resources