Skip to content

Comprehensive Fix Tasks #3

Description

@vapvarun

🚨 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);

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions