🚨 CRITICAL SECURITY FIXES
Task 1: Add Direct File Access Protection
Priority: IMMEDIATE
Files: All PHP files missing protection
// Add to the top of EVERY PHP file after opening <?php tag
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
Files to update:
admin/partials/email-customizer-for-woocommerce-admin-display.php
admin/partials/email-customizer-for-woocommerce-admin-display-template-one.php
admin/partials/email-customizer-for-woocommerce-admin-display-template-two.php
admin/partials/email-customizer-for-woocommerce-admin-display-template-three.php
templates/emails/email-header.php
Task 2: Implement Comprehensive Nonce Verification
Priority: IMMEDIATE
File: admin/class-email-customizer-for-woocommerce-admin.php
// Current problematic code in wb_email_customizer_add_styles()
// BEFORE: No nonce verification for style generation
// FIXED VERSION:
public function wb_email_customizer_add_styles($styles) {
// Only verify nonce for admin customizer requests
if (is_admin() && isset($_GET['customize_changeset_uuid'])) {
if (!isset($_GET['_wpnonce']) || !wp_verify_nonce(wp_unslash($_GET['_wpnonce']), 'preview-mail')) {
return $styles;
}
}
// If it's a preview email with nonce parameter
if (isset($_GET['nonce'])) {
if (!wp_verify_nonce(wp_unslash($_GET['nonce']), '_wc_email_customizer_send_email_nonce')) {
return $styles;
}
}
// Rest of the function...
}
Task 3: Add Comprehensive Capability Checks
Priority: IMMEDIATE
// Add this method to the admin class
private function verify_user_permissions(): bool {
if (!current_user_can('manage_woocommerce')) {
wp_die(
esc_html__('You do not have sufficient permissions to access this feature.', 'email-customizer-for-woocommerce'),
esc_html__('Access Denied', 'email-customizer-for-woocommerce'),
array('response' => 403)
);
return false;
}
return true;
}
// Add capability checks to all admin functions
public function wb_email_customizer_admin_options_page() {
if (!$this->verify_user_permissions()) {
return;
}
// Rest of function...
}
public function wb_email_customizer_add_sections($wp_customize) {
if (!current_user_can('customize')) {
return;
}
// Rest of function...
}
Task 4: Implement Secure Input Validation
Priority: IMMEDIATE
// Create a comprehensive validation class
class WB_Email_Customizer_Validator {
/**
* Validate email template selection
*/
public static function validate_template($template): string {
$allowed_templates = ['default', 'template-one', 'template-two', 'template-three'];
$template = sanitize_key($template);
return in_array($template, $allowed_templates, true) ? $template : 'default';
}
/**
* Validate color values
*/
public static function validate_color($color): string {
$color = sanitize_hex_color($color);
return $color ? $color : '#ffffff';
}
/**
* Validate numeric values with range
*/
public static function validate_numeric($value, int $min = 0, int $max = 100): int {
$value = absint($value);
return max($min, min($max, $value));
}
/**
* Validate text with length limit
*/
public static function validate_text($text, int $max_length = 500): string {
$text = sanitize_text_field($text);
return mb_substr($text, 0, $max_length, 'UTF-8');
}
/**
* Validate URL
*/
public static function validate_url($url): string {
return esc_url_raw($url);
}
/**
* Validate alignment options
*/
public static function validate_alignment($alignment): string {
$allowed = ['left', 'center', 'right'];
return in_array($alignment, $allowed, true) ? $alignment : 'center';
}
/**
* Validate border style
*/
public static function validate_border_style($style): string {
$allowed = ['solid', 'dashed', 'dotted', 'double', 'none'];
return in_array($style, $allowed, true) ? $style : 'solid';
}
/**
* Validate font family
*/
public static function validate_font_family($font): string {
$allowed = ['sans-serif', 'serif'];
return in_array($font, $allowed, true) ? $font : 'sans-serif';
}
}
// Update the parameter retrieval function
private function get_validated_param($key, $default = '', $validation_type = 'text') {
if (!isset($_GET[$key])) {
return get_option($key, $default);
}
$value = wp_unslash($_GET[$key]);
switch ($validation_type) {
case 'color':
return WB_Email_Customizer_Validator::validate_color($value);
case 'numeric':
return WB_Email_Customizer_Validator::validate_numeric($value);
case 'url':
return WB_Email_Customizer_Validator::validate_url($value);
case 'template':
return WB_Email_Customizer_Validator::validate_template($value);
case 'alignment':
return WB_Email_Customizer_Validator::validate_alignment($value);
case 'border_style':
return WB_Email_Customizer_Validator::validate_border_style($value);
case 'font_family':
return WB_Email_Customizer_Validator::validate_font_family($value);
default:
return WB_Email_Customizer_Validator::validate_text($value);
}
}
⚙️ WORDPRESS CUSTOMIZER STANDARDS COMPLIANCE
Task 5: Fix Customizer Control Implementation
Priority: HIGH
Issue: Non-standard customizer control implementation
// CURRENT PROBLEMATIC CODE:
// Controls are created without proper sanitization and validation
// FIXED VERSION - Update wb_email_customizer_add_customizer_settings():
public function wb_email_customizer_add_customizer_settings($wp_customize) {
// Email Template Setting with proper validation
$wp_customize->add_setting(
'woocommerce_email_template',
array(
'type' => 'option',
'default' => 'default',
'transport' => 'postMessage',
'sanitize_callback' => array($this, 'sanitize_template_choice'),
'validate_callback' => array($this, 'validate_template_choice'),
)
);
// Color settings with proper sanitization
$wp_customize->add_setting(
'woocommerce_email_header_background_color',
array(
'type' => 'option',
'default' => '#557da1',
'transport' => 'postMessage',
'sanitize_callback' => 'sanitize_hex_color',
'validate_callback' => array($this, 'validate_color_value'),
)
);
// Numeric settings with range validation
$wp_customize->add_setting(
'woocommerce_email_header_font_size',
array(
'type' => 'option',
'default' => 30,
'transport' => 'postMessage',
'sanitize_callback' => array($this, 'sanitize_font_size'),
'validate_callback' => array($this, 'validate_font_size'),
)
);
// Text settings with length limits
$wp_customize->add_setting(
'woocommerce_email_heading_text',
array(
'type' => 'option',
'default' => __('Thank you for your order', 'email-customizer-for-woocommerce'),
'transport' => 'postMessage',
'sanitize_callback' => array($this, 'sanitize_heading_text'),
'validate_callback' => array($this, 'validate_heading_text'),
)
);
}
// Add sanitization callbacks
public function sanitize_template_choice($value) {
return WB_Email_Customizer_Validator::validate_template($value);
}
public function validate_template_choice($validity, $value) {
$allowed_templates = ['default', 'template-one', 'template-two', 'template-three'];
if (!in_array($value, $allowed_templates, true)) {
$validity->add('invalid_template', __('Invalid template selection.', 'email-customizer-for-woocommerce'));
}
return $validity;
}
public function sanitize_font_size($value) {
return WB_Email_Customizer_Validator::validate_numeric($value, 10, 50);
}
public function validate_font_size($validity, $value) {
if ($value < 10 || $value > 50) {
$validity->add('invalid_font_size', __('Font size must be between 10 and 50 pixels.', 'email-customizer-for-woocommerce'));
}
return $validity;
}
public function sanitize_heading_text($value) {
return WB_Email_Customizer_Validator::validate_text($value, 200);
}
public function validate_heading_text($validity, $value) {
if (strlen($value) > 200) {
$validity->add('text_too_long', __('Heading text is too long. Maximum 200 characters allowed.', 'email-customizer-for-woocommerce'));
}
return $validity;
}
public function validate_color_value($validity, $value) {
if (!sanitize_hex_color($value)) {
$validity->add('invalid_color', __('Please enter a valid hex color code.', 'email-customizer-for-woocommerce'));
}
return $validity;
}
Task 6: Implement Standard Image-Based Radio Controls
Priority: HIGH
// Replace custom image radio control with standard WordPress approach
public function wb_email_customizer_add_controls($wp_customize) {
// Step 1: Use standard radio control for template selection
$wp_customize->add_control(
'woocommerce_email_template_control',
array(
'type' => 'radio',
'label' => __('Email Template', 'email-customizer-for-woocommerce'),
'description' => __('Choose an email template design.', 'email-customizer-for-woocommerce'),
'section' => 'wc_email_templates',
'settings' => 'woocommerce_email_template',
'choices' => array(
'default' => __('Default Template', 'email-customizer-for-woocommerce'),
'template-one' => __('Modern Template', 'email-customizer-for-woocommerce'),
'template-two' => __('Minimal Template', 'email-customizer-for-woocommerce'),
'template-three' => __('Bold Template', 'email-customizer-for-woocommerce'),
),
)
);
// Use standard color control
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'wc_email_header_color_control',
array(
'label' => __('Header Background Color', 'email-customizer-for-woocommerce'),
'description' => __('Choose the background color for email headers.', 'email-customizer-for-woocommerce'),
'section' => 'wc_email_header',
'settings' => 'woocommerce_email_header_background_color',
)
)
);
// Range control with proper attributes
$wp_customize->add_control(
'wc_email_header_font_size_control',
array(
'type' => 'range',
'label' => __('Header Font Size', 'email-customizer-for-woocommerce'),
'description' => __('Set the font size for email headers (10-50px).', 'email-customizer-for-woocommerce'),
'section' => 'wc_email_header',
'settings' => 'woocommerce_email_header_font_size',
'input_attrs' => array(
'min' => 10,
'max' => 50,
'step' => 1,
),
)
);
// Text control with proper validation
$wp_customize->add_control(
'wc_email_heading_text_control',
array(
'type' => 'text',
'label' => __('Email Heading', 'email-customizer-for-woocommerce'),
'description' => __('Enter the main heading text for emails (max 200 characters).', 'email-customizer-for-woocommerce'),
'section' => 'wc_email_text',
'settings' => 'woocommerce_email_heading_text',
'input_attrs' => array(
'maxlength' => 200,
'placeholder' => __('Enter heading text...', 'email-customizer-for-woocommerce'),
),
)
);
}
// Step 2: Add CSS for image-based radio styling
public function enqueue_customizer_styles() {
if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) {
$extension = is_rtl() ? '.rtl.css' : '.css';
$path = is_rtl() ? '/rtl' : '';
} else {
$extension = is_rtl() ? '.rtl.css' : '.min.css';
$path = is_rtl() ? '/rtl' : '/min';
}
wp_enqueue_style(
'wb-email-customizer-customizer-styles',
EMAIL_CUSTOMIZER_FOR_WOOCOMMERCE_PLUGIN_URL . 'admin/css' . $path . '/customizer-image-radio' . $extension,
array(),
EMAIL_CUSTOMIZER_FOR_WOOCOMMERCE_VERSION
);
}
// Step 3: Add to constructor
public function __construct($plugin_name, $version) {
// ... existing code ...
add_action('customize_controls_enqueue_scripts', array($this, 'enqueue_customizer_styles'));
}
Task 6A: Create CSS File for Image-Based Radio Controls
Priority: HIGH
File: Create admin/css/customizer-image-radio.css
/* ===== EMAIL TEMPLATE IMAGE RADIO STYLES ===== */
/* Hide actual radio inputs for template selection */
.customize-control-radio#customize-control-woocommerce_email_template_control input[type="radio"] {
display: none !important;
}
/* Style labels as image boxes */
.customize-control-radio#customize-control-woocommerce_email_template_control label {
display: inline-block;
cursor: pointer;
margin: 10px 15px 10px 0;
position: relative;
vertical-align: top;
}
/* Image preview boxes */
.customize-control-radio#customize-control-woocommerce_email_template_control label::before {
content: "";
display: block;
width: 120px;
height: 90px;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border: 3px solid #ddd;
border-radius: 8px;
transition: all 0.3s ease-in-out;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Template image assignments */
.customize-control-radio#customize-control-woocommerce_email_template_control label[for*="default"]::before {
background-image: url('../img/woo_default_template.jpg');
}
.customize-control-radio#customize-control-woocommerce_email_template_control label[for*="template-one"]::before {
background-image: url('../img/woo_full_template.jpg');
}
.customize-control-radio#customize-control-woocommerce_email_template_control label[for*="template-two"]::before {
background-image: url('../img/woo_skinny_template.jpg');
}
.customize-control-radio#customize-control-woocommerce_email_template_control label[for*="template-three"]::before {
background-image: url('../img/woo_flat_template.jpg');
}
/* Selected state styling */
.customize-control-radio#customize-control-woocommerce_email_template_control input[type="radio"]:checked + label::before {
border-color: #0073aa;
box-shadow: 0 0 0 2px rgba(0,115,170,0.3), 0 4px 8px rgba(0,0,0,0.15);
transform: scale(1.02);
}
/* Hover effects */
.customize-control-radio#customize-control-woocommerce_email_template_control label:hover::before {
border-color: #0073aa;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
transform: translateY(-1px);
}
/* Label text styling */
.customize-control-radio#customize-control-woocommerce_email_template_control label {
text-align: center;
font-size: 12px;
font-weight: 500;
color: #555;
}
/* Add checkmark for selected state */
.customize-control-radio#customize-control-woocommerce_email_template_control input[type="radio"]:checked + label::after {
content: "✓";
position: absolute;
top: 5px;
right: 5px;
background: #0073aa;
color: white;
border-radius: 50%;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
font-size: 12px;
font-weight: bold;
}
/* Responsive design */
@media (max-width: 640px) {
.customize-control-radio#customize-control-woocommerce_email_template_control label::before {
width: 100px;
height: 75px;
}
.customize-control-radio#customize-control-woocommerce_email_template_control label {
margin: 5px 10px 10px 0;
}
}
/* Loading state */
.customize-control-radio#customize-control-woocommerce_email_template_control.loading label::before {
opacity: 0.5;
background-image: none;
background-color: #f0f0f0;
}
.customize-control-radio#customize-control-woocommerce_email_template_control.loading label::after {
content: "Loading...";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 11px;
color: #666;
}
/* Focus states for accessibility */
.customize-control-radio#customize-control-woocommerce_email_template_control input[type="radio"]:focus + label::before {
outline: 2px solid #0073aa;
outline-offset: 2px;
}
/* High contrast mode support */
@media (prefers-contrast: high) {
.customize-control-radio#customize-control-woocommerce_email_template_control label::before {
border-width: 2px;
border-color: #000;
}
.customize-control-radio#customize-control-woocommerce_email_template_control input[type="radio"]:checked + label::before {
border-color: #000;
background-color: #fff;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.customize-control-radio#customize-control-woocommerce_email_template_control label::before {
border-color: #666;
}
.customize-control-radio#customize-control-woocommerce_email_template_control label {
color: #ccc;
}
}
Task 6B: Create Minified CSS Version
File: Create admin/css/min/customizer-image-radio.min.css
.customize-control-radio#customize-control-woocommerce_email_template_control input[type="radio"]{display:none!important}.customize-control-radio#customize-control-woocommerce_email_template_control label{display:inline-block;cursor:pointer;margin:10px 15px 10px 0;position:relative;vertical-align:top;text-align:center;font-size:12px;font-weight:500;color:#555}.customize-control-radio#customize-control-woocommerce_email_template_control label::before{content:"";display:block;width:120px;height:90px;background-size:cover;background-position:center;background-repeat:no-repeat;border:3px solid #ddd;border-radius:8px;transition:all .3s ease-in-out;box-shadow:0 2px 4px rgba(0,0,0,.1)}.customize-control-radio#customize-control-woocommerce_email_template_control label[for*="default"]::before{background-image:url('../img/woo_default_template.jpg')}.customize-control-radio#customize-control-woocommerce_email_template_control label[for*="template-one"]::before{background-image:url('../img/woo_full_template.jpg')}.customize-control-radio#customize-control-woocommerce_email_template_control label[for*="template-two"]::before{background-image:url('../img/woo_skinny_template.jpg')}.customize-control-radio#customize-control-woocommerce_email_template_control label[for*="template-three"]::before{background-image:url('../img/woo_flat_template.jpg')}.customize-control-radio#customize-control-woocommerce_email_template_control input[type="radio"]:checked+label::before{border-color:#0073aa;box-shadow:0 0 0 2px rgba(0,115,170,.3),0 4px 8px rgba(0,0,0,.15);transform:scale(1.02)}.customize-control-radio#customize-control-woocommerce_email_template_control label:hover::before{border-color:#0073aa;box-shadow:0 2px 8px rgba(0,0,0,.15);transform:translateY(-1px)}.customize-control-radio#customize-control-woocommerce_email_template_control input[type="radio"]:checked+label::after{content:"✓";position:absolute;top:5px;right:5px;background:#0073aa;color:#fff;border-radius:50%;width:20px;height:20px;line-height:20px;text-align:center;font-size:12px;font-weight:700}.customize-control-radio#customize-control-woocommerce_email_template_control input[type="radio"]:focus+label::before{outline:2px solid #0073aa;outline-offset:2px}@media (max-width:640px){.customize-control-radio#customize-control-woocommerce_email_template_control label::before{width:100px;height:75px}.customize-control-radio#customize-control-woocommerce_email_template_control label{margin:5px 10px 10px 0}}
Task 6C: Remove Old Custom Control Class
Priority: HIGH
File: Remove includes/class-email-customizer-for-woocommerce-radio-image.php
// This entire file should be deleted as it's no longer needed
// The functionality is now handled by standard WordPress controls + CSS
Update main plugin file to remove the include:
// In woocommerce-email-customizer.php
// REMOVE this line:
// require_once plugin_dir_path( __FILE__ ) . 'includes/class-email-customizer-for-woocommerce-radio-image.php';
Priority: HIGH
// Update section creation with proper context and capabilities
public function wb_email_customizer_add_sections($wp_customize) {
$email_trigger_check = isset($_GET['email-customizer-for-woocommerce']) ?
sanitize_text_field(wp_unslash($_GET['email-customizer-for-woocommerce'])) : '';
if (!is_user_logged_in() || $email_trigger_check !== 'true') {
return;
}
// Main panel with proper description
$wp_customize->add_panel(
'wc_email_customizer_panel',
array(
'title' => __('WooCommerce Email Customizer', 'email-customizer-for-woocommerce'),
'description' => __('Customize the appearance and content of your WooCommerce emails.', 'email-customizer-for-woocommerce'),
'capability' => 'manage_woocommerce',
'priority' => 10,
)
);
// Template section with warning
$wp_customize->add_section(
'wc_email_templates',
array(
'title' => __('Email Templates', 'email-customizer-for-woocommerce'),
'description' => $this->get_template_section_description(),
'capability' => 'manage_woocommerce',
'priority' => 10,
'panel' => 'wc_email_customizer_panel',
)
);
// Add all other sections with proper descriptions
$sections = array(
'wc_email_text' => array(
'title' => __('Email Content', 'email-customizer-for-woocommerce'),
'description' => __('Customize the text content of your emails.', 'email-customizer-for-woocommerce'),
'priority' => 20,
),
'wc_email_header' => array(
'title' => __('Email Header', 'email-customizer-for-woocommerce'),
'description' => __('Customize the header section of your emails including logo and styling.', 'email-customizer-for-woocommerce'),
'priority' => 30,
),
'wc_email_appearance_customizer' => array(
'title' => __('Container & Layout', 'email-customizer-for-woocommerce'),
'description' => __('Adjust the overall layout, spacing, and container styling.', 'email-customizer-for-woocommerce'),
'priority' => 40,
),
'wc_email_body' => array(
'title' => __('Email Body', 'email-customizer-for-woocommerce'),
'description' => __('Customize the main content area styling and typography.', 'email-customizer-for-woocommerce'),
'priority' => 50,
),
'wc_email_footer' => array(
'title' => __('Email Footer', 'email-customizer-for-woocommerce'),
'description' => __('Customize the footer section including text and styling.', 'email-customizer-for-woocommerce'),
'priority' => 60,
),
);
foreach ($sections as $section_id => $section_args) {
$section_args['capability'] = 'manage_woocommerce';
$section_args['panel'] = 'wc_email_customizer_panel';
$wp_customize->add_section($section_id, $section_args);
}
}
private function get_template_section_description(): string {
return '<div class="wc-template-warning" style="background: #fff3cd; border: 1px solid #ffb900; border-radius: 4px; padding: 15px; margin: 0 0 20px 0;">' .
'<div style="display: flex; align-items: flex-start; gap: 10px;">' .
'<span style="font-size: 18px; color: #8a6914;">⚠️</span>' .
'<div>' .
'<strong style="color: #8a6914; font-size: 14px; display: block; margin-bottom: 5px;">' .
__('Template Override Warning', 'email-customizer-for-woocommerce') . '</strong>' .
'<p style="margin: 0; font-size: 13px; line-height: 1.4; color: #6c5b00;">' .
__('Selecting a template will immediately override all current email styling settings. Your customizations will be replaced with the template\'s default values.', 'email-customizer-for-woocommerce') .
'</p></div></div></div>';
}
🌍 INTERNATIONALIZATION & LOCALIZATION
Task 8: Complete Text Domain Implementation
Priority: HIGH
Issue: Many hardcoded strings without translation
// Fix hardcoded strings in JavaScript files
// File: admin/js/customizer-wbpreview.js
// BEFORE: console.log(newval);
// AFTER: Add localization
// In PHP file, localize the script:
wp_localize_script('woocommerce-email-customizer-live-preview', 'wc_email_customizer_i18n', array(
'template_changed' => __('Template changed to:', 'email-customizer-for-woocommerce'),
'settings_updated' => __('Settings updated:', 'email-customizer-for-woocommerce'),
'loading' => __('Loading...', 'email-customizer-for-woocommerce'),
'error_occurred' => __('An error occurred. Please try again.', 'email-customizer-for-woocommerce'),
));
// Update JavaScript to use localized strings:
// console.log('Template changed to: ' + newval);
// BECOMES:
// console.log(wc_email_customizer_i18n.template_changed + ' ' + newval);
Task 9: Add Missing Translation Strings
Priority: HIGH
// Update all hardcoded strings throughout the codebase
// File: admin/class-email-customizer-for-woocommerce-admin.php
// BEFORE: Hardcoded validation messages
if ($validity->add('invalid_template', 'Invalid template selection.')) {
// AFTER: Translatable validation messages
if ($validity->add('invalid_template', __('Invalid template selection.', 'email-customizer-for-woocommerce'))) {
// Add context for translators
$template_warning = _x(
'Template Override Warning',
'Warning message title in customizer',
'email-customizer-for-woocommerce'
);
$override_message = _x(
'Selecting a template will immediately override all current email styling settings.',
'Template override warning description',
'email-customizer-for-woocommerce'
);
// Add plural forms where needed
$items_count = count($order->get_items());
$items_text = sprintf(
_n(
'You have %d item in your order.',
'You have %d items in your order.',
$items_count,
'email-customizer-for-woocommerce'
),
$items_count
);
Task 10: Create Translation-Ready Template Files
Priority: MEDIUM
// Update template files to be translation-ready
// File: admin/partials/email-customizer-for-woocommerce-admin-display.php
// BEFORE:
// echo 'Order #2020';
// AFTER:
printf(
/* translators: %s: Order number */
esc_html__('Order #%s', 'email-customizer-for-woocommerce'),
esc_html('2020')
);
// BEFORE:
// echo 'John Doe<br />1234 Fake Street<br />WooVille, SA';
// AFTER:
$demo_address = array(
'name' => __('John Doe', 'email-customizer-for-woocommerce'),
'street' => __('1234 Fake Street', 'email-customizer-for-woocommerce'),
'city' => __('WooVille, SA', 'email-customizer-for-woocommerce'),
);
foreach ($demo_address as $line) {
echo esc_html($line) . '<br />';
}
🎯 PERFORMANCE OPTIMIZATIONS
Task 11: Implement Comprehensive Caching
Priority: HIGH
// Create a caching class
class WB_Email_Customizer_Cache {
private static $option_cache = array();
private static $style_cache = array();
/**
* Get cached option or retrieve and cache it
*/
public static function get_option($option_name, $default = false) {
if (!isset(self::$option_cache[$option_name])) {
self::$option_cache[$option_name] = get_option($option_name, $default);
}
return self::$option_cache[$option_name];
}
/**
* Get cached email styles
*/
public static function get_email_styles($cache_key) {
if (isset(self::$style_cache[$cache_key])) {
return self::$style_cache[$cache_key];
}
// Try transient cache first
$cached = get_transient('wc_email_styles_' . $cache_key);
if ($cached !== false) {
self::$style_cache[$cache_key] = $cached;
return $cached;
}
return false;
}
/**
* Set cached email styles
*/
public static function set_email_styles($cache_key, $styles, $expiration = 3600) {
self::$style_cache[$cache_key] = $styles;
set_transient('wc_email_styles_' . $cache_key, $styles, $expiration);
}
/**
* Clear all caches
*/
public static function clear_cache() {
self::$option_cache = array();
self::$style_cache = array();
delete_transient('wc_email_styles_*');
}
}
// Update the styles function to use caching
public function wb_email_customizer_add_styles($styles) {
// Create cache key from request parameters
$cache_params = array_intersect_key($_GET, array_flip([
'woocommerce_email_template',
'woocommerce_email_background_color',
'woocommerce_email_header_background_color',
// ... add all relevant parameters
]));
$cache_key = md5(serialize($cache_params));
// Try to get cached styles
$cached_styles = WB_Email_Customizer_Cache::get_email_styles($cache_key);
if ($cached_styles !== false) {
return $styles . $cached_styles;
}
// Generate styles if not cached
$generated_styles = $this->generate_email_styles();
// Cache the generated styles
WB_Email_Customizer_Cache::set_email_styles($cache_key, $generated_styles);
return $styles . $generated_styles;
}
Task 12: Optimize Database Queries
Priority: HIGH
// Batch load all options at once
private function get_all_email_options() {
static $all_options = null;
if ($all_options === null) {
$option_names = [
'woocommerce_email_template',
'woocommerce_email_background_color',
'woocommerce_email_body_background_color',
'woocommerce_email_header_background_color',
'woocommerce_email_header_text_color',
'woocommerce_email_body_text_color',
'woocommerce_email_link_color',
'woocommerce_email_footer_text_color',
'woocommerce_email_border_color',
'woocommerce_email_body_border_color',
'woocommerce_email_footer_address_border_color',
'woocommerce_email_footer_background_color',
'woocommerce_email_footer_address_background_color',
// ... add all option names
];
// Get all options in a single query using wp_cache
$all_options = array();
foreach ($option_names as $option_name) {
$all_options[$option_name] = WB_Email_Customizer_Cache::get_option($option_name);
}
}
return $all_options;
}
// Update the parameter retrieval to use cached options
private function get_param_with_cache($key, $default = '', $validation_type = 'text') {
if (isset($_GET[$key])) {
return $this->get_validated_param($key, $default, $validation_type);
}
$all_options = $this->get_all_email_options();
return isset($all_options[$key]) ? $all_options[$key] : $default;
}
🔧 CODE QUALITY IMPROVEMENTS
Task 13: Add Comprehensive Error Handling
Priority: MEDIUM
// Add error handling and logging class
class WB_Email_Customizer_Logger {
/**
* Log error with context
*/
public static function log_error($message, $context = array()) {
if (defined('WP_DEBUG') && WP_DEBUG) {
$log_message = sprintf(
'[WC Email Customizer] %s - Context: %s',
$message,
wp_json_encode($context)
);
error_log($log_message);
}
}
/**
* Log info for debugging
*/
public static function log_info($message, $context = array()) {
if (defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
$log_message = sprintf(
'[WC Email Customizer INFO] %s - Context: %s',
$message,
wp_json_encode($context)
);
error_log($log_message);
}
}
}
// Add error handling to critical functions
public function wb_email_customizer_load_email_template($wp_query) {
try {
if (get_query_var($this->email_trigger)) {
static $already_executed = false;
if ($already_executed) {
return $wp_query;
}
$mailer = WC()->mailer();
if (!$mailer) {
throw new Exception('WooCommerce mailer not available');
}
ob_start();
$template = $this->get_validated_param('woocommerce_email_template', 'default', 'template');
$template_file = $this->get_template_file_path($template);
if (!file_exists($template_file)) {
throw new Exception('Template file not found: ' . $template_file);
}
include $template_file;
$already_executed = true;
$message = ob_get_clean();
if (empty($message)) {
throw new Exception('Empty email template generated');
}
$email_heading = $this->get_validated_param('woocommerce_email_heading_text', __('Thanks for your order!', 'email-customizer-for-woocommerce'), 'text');
$email = new WC_Email();
$messages = $email->style_inline($mailer->wrap_message($email_heading, $message));
echo $messages; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
exit;
}
} catch (Exception $e) {
WB_Email_Customizer_Logger::log_error('Email template loading failed', array(
'error' => $e->getMessage(),
'template' => $template ?? 'unknown',
'user_id' => get_current_user_id(),
));
// Return gracefully
return $wp_query;
}
return $wp_query;
}
Task 14: Add PHP 8.0+ Type Declarations
Priority: MEDIUM
// Update class with proper type declarations
class Email_Customizer_For_Woocommerce_Admin {
private string $plugin_name;
private string $email_trigger;
private string $version;
public array $plugin_settings_tabs;
public function __construct(string $plugin_name, string $version) {
$this->plugin_name = $plugin_name;
$this->version = $version;
$this->email_trigger = 'email-customizer-for-woocommerce';
$this->plugin_settings_tabs = array();
add_action('init', [$this, 'wb_email_customizer_maybe_run_email_customizer']);
}
public function enqueue_styles(string $hook): void {
$plugin_pages = [
'wbcomplugins',
'wb-email-customizer-settings',
'woocommerce_page_wc-settings'
];
$current_screen = get_current_screen();
if (!$current_screen ||
(!in_array($current_screen->id, $plugin_pages, true) &&
strpos($hook, 'email-customizer') === false)) {
return;
}
$this->enqueue_plugin_styles();
}
private function enqueue_plugin_styles(): void {
$extension = $this->get_style_extension();
$path = $this->get_style_path();
wp_enqueue_style(
$this->plugin_name,
plugin_dir_url(__FILE__) . 'css' . $path . '/email-customizer-for-woocommerce-admin' . $extension,
array(),
$this->version,
'all'
);
}
private function get_style_extension(): string {
if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) {
return is_rtl() ? '.rtl.css' : '.css';
}
return is_rtl() ? '.rtl.css' : '.min.css';
}
private function get_style_path(): string {
if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) {
return is_rtl() ? '/rtl' : '';
}
return is_rtl() ? '/rtl' : '/min';
}
private function verify_user_permissions(): bool {
if (!current_user_can('manage_woocommerce')) {
wp_die(
esc_html__('You do not have sufficient permissions to access this feature.', 'email-customizer-for-woocommerce'),
esc_html__('Access Denied', 'email-customizer-for-woocommerce'),
array('response' => 403)
);
return false;
}
return true;
}
}
Task 15: Implement Proper AJAX Handling
Priority: MEDIUM
// Add proper AJAX handler with security
public function wb_email_customizer_send_email(): void {
// Verify nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(wp_unslash($_POST['nonce']), '_wc_email_customizer_send_email_nonce')) {
wp_send_json_error(array(
'message' => __('Security check failed. Please refresh the page and try again.', 'email-customizer-for-woocommerce')
));
return;
}
// Check capabilities
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array(
'message' => __('You do not have permission to send test emails.', 'email-customizer-for-woocommerce')
));
return;
}
// Validate email address
$email = isset($_POST['email']) ? sanitize_email(wp_unslash($_POST['email'])) : '';
if (!is_email($email)) {
wp_send_json_error(array(
'message' => __('Please enter a valid email address.', 'email-customizer-for-woocommerce')
));
return;
}
try {
// Rate limiting
$user_id = get_current_user_id();
$rate_limit_key = 'wc_email_test_' . $user_id;
$sent_count = get_transient($rate_limit_key);
if ($sent_count !== false && $sent_count >= 5) {
wp_send_json_error(array(
'message' => __('Rate limit exceeded. Please wait before sending another test email.', 'email-customizer-for-woocommerce')
));
return;
}
// Send test email
$email_sent = $this->send_test_email($email);
if ($email_sent) {
// Update rate limiting
$new_count = $sent_count !== false ? $sent_count + 1 : 1;
set_transient($rate_limit_key, $new_count, 3600); // 1 hour
wp_send_json_success(array(
'message' => sprintf(
/* translators: %s: Email address */
__('Test email sent successfully to %s', 'email-customizer-for-woocommerce'),
esc_html($email)
)
));
} else {
wp_send_json_error(array(
'message' => __('Failed to send test email. Please check your email configuration.', 'email-customizer-for-woocommerce')
));
}
} catch (Exception $e) {
WB_Email_Customizer_Logger::log_error('AJAX email send failed', array(
'error' => $e->getMessage(),
'email' => $email,
'user_id' => get_current_user_id(),
));
wp_send_json_error(array(
'message' => __('An unexpected error occurred. Please try again.', 'email-customizer-for-woocommerce')
));
}
}
private function send_test_email(string $email): bool {
// Implementation for sending test email
$subject = __('WooCommerce Email Test', 'email-customizer-for-woocommerce');
$message = $this->generate_test_email_content();
$headers = array('Content-Type: text/html; charset=UTF-8');
return wp_mail($email, $subject, $message, $headers);
}
🧪 TESTING & VALIDATION
Task 16: Add Unit Tests Structure
Priority: MEDIUM
// Create tests directory structure and basic test file
// File: tests/test-email-customizer-admin.php
class Test_Email_Customizer_Admin extends WP_UnitTestCase {
private $admin_instance;
public function setUp(): void {
parent::setUp();
$this->admin_instance = new Email_Customizer_For_Woocommerce_Admin('test-plugin', '1.0.0');
}
public function test_template_validation(): void {
$this->assertEquals('default', WB_Email_Customizer_Validator::validate_template('invalid-template'));
$this->assertEquals('template-one', WB_Email_Customizer_Validator::validate_template('template-one'));
}
public function test_color_validation(): void {
$this->assertEquals('#ffffff', WB_Email_Customizer_Validator::validate_color('#ffffff'));
$this->assertEquals('#ffffff', WB_Email_Customizer_Validator::validate_color('invalid-color'));
}
public function test_numeric_validation(): void {
$this->assertEquals(25, WB_Email_Customizer_Validator::validate_numeric(25, 10, 50));
$this->assertEquals(10, WB_Email_Customizer_Validator::validate_numeric(5, 10, 50));
$this->assertEquals(50, WB_Email_Customizer_Validator::validate_numeric(100, 10, 50));
}
public function test_capability_check(): void {
$user = $this->factory->user->create(array('role' => 'customer'));
wp_set_current_user($user);
$this->assertFalse(current_user_can('manage_woocommerce'));
$admin_user = $this->factory->user->create(array('role' => 'administrator'));
wp_set_current_user($admin_user);
$this->assertTrue(current_user_can('manage_woocommerce'));
}
}
📱 USER EXPERIENCE IMPROVEMENTS
Task 17: Add Better User Feedback
Priority: MEDIUM
// Add admin notices for better user feedback
public function add_admin_notices(): void {
add_action('admin_notices', array($this, 'show_customizer_notices'));
}
public function show_customizer_notices(): void {
$screen = get_current_screen();
if (!$screen || strpos($screen->id, 'email-customizer') === false) {
return;
}
// Show success message after settings save
if (isset($_GET['settings-updated']) && $_GET['settings-updated'] === 'true') {
printf(
'<div class="notice notice-success is-dismissible"><p>%s</p></div>',
esc_html__('Email customizer settings saved successfully!', 'email-customizer-for-woocommerce')
);
}
// Show warning if WooCommerce emails are disabled
if (get_option('woocommerce_email_enabled') === 'no') {
printf(
'<div class="notice notice-warning"><p>%s <a href="%s">%s</a></p></div>',
esc_html__('WooCommerce emails are currently disabled.', 'email-customizer-for-woocommerce'),
esc_url(admin_url('admin.php?page=wc-settings&tab=email')),
esc_html__('Enable them here', 'email-customizer-for-woocommerce')
);
}
}
Task 18: Improve JavaScript Error Handling
Priority: MEDIUM
// Update customizer-wbpreview.js with better error handling
(function($) {
'use strict';
$(document).ready(function() {
let argument_obj = {};
argument_obj['nonce'] = woocommerce_email_customizer_controls_local.ajaxSendEmailNonce;
// Add error handling wrapper
function handleCustomizerError(error, context) {
console.error('Email Customizer Error:', error, 'Context:', context);
// Show user-friendly error message
if (wp.customize && wp.customize.notifications) {
wp.customize.notifications.add('email_customizer_error', new wp.customize.Notification(
'email_customizer_error',
{
message: wc_email_customizer_i18n.error_occurred,
type: 'error'
}
));
}
}
function updateEmailPreviewFrame(args = {}) {
try {
const iframe = document.querySelector('iframe[title="Site Preview"]');
if (!iframe) {
throw new Error('Preview iframe not found');
}
let baseUrl = iframe.getAttribute('data-src') || iframe.src;
if (!baseUrl) {
throw new Error('Invalid iframe source');
}
let url = new URL(baseUrl);
// Validate arguments
Object.keys(args).forEach((key) => {
if (args[key] !== null && args[key] !== undefined) {
url.searchParams.set(key, args[key]);
}
});
iframe.src = url.toString();
} catch (error) {
handleCustomizerError(error, { args: args });
}
}
// Add validation for customizer bindings
function bindCustomizerSetting(settingName, callback) {
try {
if (wp.customize && wp.customize(settingName)) {
wp.customize(settingName, function(value) {
try {
value.bind(callback);
} catch (error) {
handleCustomizerError(error, { setting: settingName });
}
});
} else {
console.warn('Customizer setting not found:', settingName);
}
} catch (error) {
handleCustomizerError(error, { setting: settingName });
}
}
// Update all customizer bindings to use the safe wrapper
bindCustomizerSetting('woocommerce_email_heading_text', function(newval) {
argument_obj['woocommerce_email_heading_text'] = newval;
updateEmailPreviewFrame(argument_obj);
});
// ... continue for all other settings
});
})(jQuery);
🚨 CRITICAL SECURITY FIXES
Task 1: Add Direct File Access Protection
Priority: IMMEDIATE
Files: All PHP files missing protection
Files to update:
admin/partials/email-customizer-for-woocommerce-admin-display.phpadmin/partials/email-customizer-for-woocommerce-admin-display-template-one.phpadmin/partials/email-customizer-for-woocommerce-admin-display-template-two.phpadmin/partials/email-customizer-for-woocommerce-admin-display-template-three.phptemplates/emails/email-header.phpTask 2: Implement Comprehensive Nonce Verification
Priority: IMMEDIATE
File:
admin/class-email-customizer-for-woocommerce-admin.phpTask 3: Add Comprehensive Capability Checks
Priority: IMMEDIATE
Task 4: Implement Secure Input Validation
Priority: IMMEDIATE
⚙️ WORDPRESS CUSTOMIZER STANDARDS COMPLIANCE
Task 5: Fix Customizer Control Implementation
Priority: HIGH
Issue: Non-standard customizer control implementation
Task 6: Implement Standard Image-Based Radio Controls
Priority: HIGH
Task 6A: Create CSS File for Image-Based Radio Controls
Priority: HIGH
File: Create
admin/css/customizer-image-radio.cssTask 6B: Create Minified CSS Version
File: Create
admin/css/min/customizer-image-radio.min.cssTask 6C: Remove Old Custom Control Class
Priority: HIGH
File: Remove
includes/class-email-customizer-for-woocommerce-radio-image.phpUpdate main plugin file to remove the include:
Priority: HIGH
🌍 INTERNATIONALIZATION & LOCALIZATION
Task 8: Complete Text Domain Implementation
Priority: HIGH
Issue: Many hardcoded strings without translation
Task 9: Add Missing Translation Strings
Priority: HIGH
Task 10: Create Translation-Ready Template Files
Priority: MEDIUM
🎯 PERFORMANCE OPTIMIZATIONS
Task 11: Implement Comprehensive Caching
Priority: HIGH
Task 12: Optimize Database Queries
Priority: HIGH
🔧 CODE QUALITY IMPROVEMENTS
Task 13: Add Comprehensive Error Handling
Priority: MEDIUM
Task 14: Add PHP 8.0+ Type Declarations
Priority: MEDIUM
Task 15: Implement Proper AJAX Handling
Priority: MEDIUM
🧪 TESTING & VALIDATION
Task 16: Add Unit Tests Structure
Priority: MEDIUM
📱 USER EXPERIENCE IMPROVEMENTS
Task 17: Add Better User Feedback
Priority: MEDIUM
Task 18: Improve JavaScript Error Handling
Priority: MEDIUM