- 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>
10 KiB
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
- WordPress loads jQuery in no-conflict mode by default
- Use
jQueryglobally,$within document ready callbacks - Don't create compatibility shims - they add complexity without value
- 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
- Always test on staging before production
- Use actual user accounts for realistic testing
- Verify both UI and functionality
- Check browser console for errors
- 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);
}