# easy-invoice/2.2.0/includes/Forms/FieldRenderer.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.2.0. 912 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.2.0/code/includes/Forms/FieldRenderer.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.2.0/raw/includes/Forms/FieldRenderer.php
- Modified: 2025-08-21T13:32:50+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.2.0/code/includes/Forms/FieldRenderer.php#L10-L20`.

```php
<?php
/**
 * Field Renderer Class
 *
 * @package     EasyInvoice
 * @author      Your Name
 * @copyright   Copyright (c) 2023, Your Company
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       1.0.0
 */

namespace EasyInvoice\Forms;

/**
 * Field Renderer
 *
 * Handles rendering of all form field types for the invoice form.
 *
 * @since 1.0.0
 */
class FieldRenderer {
    
    /**
     * Render a field based on its type
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    public function renderField(array $field, $invoice = null): string {
        $type = $field['type'] ?? 'text';
        
        // Convert field type to camelCase for method name generation
        $method_name = 'render' . easy_invoice_str_replace('_', '', ucwords($type, '_')) . 'Field';
        
        if (method_exists($this, $method_name)) {
            return $this->$method_name($field, $invoice);
        }
        
        // Fallback to text field
        return $this->renderTextField($field, $invoice);
    }
    
    /**
     * Render a text field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderTextField(array $field, $invoice = null): string {
        $id = $field['id'] ?? $field['name'] ?? '';
        $name = $field['name'] ?? '';
        $label = $field['label'] ?? '';
        $placeholder = $field['placeholder'] ?? '';
        $required = $field['required'] ?? false;
        $readonly = $field['readonly'] ?? false;
        $description = $field['description'] ?? '';
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-6';
        $value = $this->getFieldValue($field, $invoice);
        
        $required_attr = $required ? 'required' : '';
        $required_class = $required ? 'required' : '';
        $readonly_attr = $readonly ? 'readonly' : '';
        $description_html = $description ? '<p class="text-gray-500 mt-1 text-xs">' . esc_html($description) . '</p>' : '';
        
        return sprintf(
            '<div class="%s">
                <label for="%s" class="block text-sm font-medium text-gray-700 %s">%s</label>
                <div class="mt-1">
                    <input type="text" 
                           id="%s" 
                           name="%s" 
                           value="%s" 
                           placeholder="%s" 
                           class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm %s" 
                           %s %s>
                    %s
                </div>
            </div>',
            esc_attr($grid_cols),
            esc_attr($id),
            esc_attr($required_class),
            esc_html($label),
            esc_attr($id),
            esc_attr($name),
            esc_attr($value),
            esc_attr($placeholder),
            esc_attr($required_class),
            $required_attr,
            $readonly_attr,
            $description_html
        );
    }
    
    /**
     * Render a hidden field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderHiddenField(array $field, $invoice = null): string {
        $id = $field['id'] ?? '';
        $name = $field['name'] ?? '';
        $value = $this->getFieldValue($field, $invoice);
        
        return sprintf(
            '<input type="hidden" id="%s" name="%s" value="%s">',
            esc_attr($id),
            esc_attr($name),
            esc_attr($value)
        );
    }
    
    /**
     * Render an email field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderEmailField(array $field, $invoice = null): string {
        $id = $field['id'] ?? '';
        $name = $field['name'] ?? '';
        $label = $field['label'] ?? '';
        $placeholder = $field['placeholder'] ?? '';
        $required = $field['required'] ?? false;
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-6';
        $value = $this->getFieldValue($field, $invoice);
        $description = $field['description'] ?? '';
        
        $required_attr = $required ? 'required' : '';
        $required_class = $required ? 'required' : '';
        $description_html = $description ? '<p class="text-gray-500 mt-1 text-xs">' . esc_html($description) . '</p>' : '';
        
        return sprintf(
            '<div class="%s">
                <label for="%s" class="block text-sm font-medium text-gray-700 %s">%s</label>
                <div class="mt-1">
                    <input type="email" 
                           id="%s" 
                           name="%s" 
                           value="%s" 
                           placeholder="%s" 
                           class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm %s" 
                           %s>
                    %s
                </div>
            </div>',
            esc_attr($grid_cols),
            esc_attr($id),
            esc_attr($required_class),
            esc_html($label),
            esc_attr($id),
            esc_attr($name),
            esc_attr($value),
            esc_attr($placeholder),
            esc_attr($required_class),
            $required_attr,
            $description_html
        );
    }
    
    /**
     * Render a telephone field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderTelField(array $field, $invoice = null): string {
        $id = $field['id'] ?? '';
        $name = $field['name'] ?? '';
        $label = $field['label'] ?? '';
        $placeholder = $field['placeholder'] ?? '';
        $required = $field['required'] ?? false;
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-6';
        $value = $this->getFieldValue($field, $invoice);
        $description = $field['description'] ?? '';
        
        $required_attr = $required ? 'required' : '';
        $required_class = $required ? 'required' : '';
        $description_html = $description ? '<p class="text-gray-500 mt-1 text-xs">' . esc_html($description) . '</p>' : '';
        
        return sprintf(
            '<div class="%s">
                <label for="%s" class="block text-sm font-medium text-gray-700 %s">%s</label>
                <div class="mt-1">
                    <input type="tel" 
                           id="%s" 
                           name="%s" 
                           value="%s" 
                           placeholder="%s" 
                           class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm %s" 
                           %s>
                    %s
                </div>
            </div>',
            esc_attr($grid_cols),
            esc_attr($id),
            esc_attr($required_class),
            esc_html($label),
            esc_attr($id),
            esc_attr($name),
            esc_attr($value),
            esc_attr($placeholder),
            esc_attr($required_class),
            $required_attr,
            $description_html
        );
    }
    
    /**
     * Render a URL field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderUrlField(array $field, $invoice = null): string {
        $id = $field['id'] ?? '';
        $name = $field['name'] ?? '';
        $label = $field['label'] ?? '';
        $placeholder = $field['placeholder'] ?? '';
        $required = $field['required'] ?? false;
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-6';
        $value = $this->getFieldValue($field, $invoice);
        $description = $field['description'] ?? '';
        
        $required_attr = $required ? 'required' : '';
        $required_class = $required ? 'required' : '';
        $description_html = $description ? '<p class="text-gray-500 mt-1 text-xs">' . esc_html($description) . '</p>' : '';
        
        return sprintf(
            '<div class="%s">
                <label for="%s" class="block text-sm font-medium text-gray-700 %s">%s</label>
                <div class="mt-1">
                    <input type="url" 
                           id="%s" 
                           name="%s" 
                           value="%s" 
                           placeholder="%s" 
                           class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm %s" 
                           %s>
                    %s
                </div>
            </div>',
            esc_attr($grid_cols),
            esc_attr($id),
            esc_attr($required_class),
            esc_html($label),
            esc_attr($id),
            esc_attr($name),
            esc_attr($value),
            esc_attr($placeholder),
            esc_attr($required_class),
            $required_attr,
            $description_html
        );
    }
    
    /**
     * Render a date field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderDateField(array $field, $invoice = null): string {
        $id = $field['id'] ?? '';
        $name = $field['name'] ?? '';
        $label = $field['label'] ?? '';
        $placeholder = $field['placeholder'] ?? '';
        $required = $field['required'] ?? false;
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-6';
        $value = $this->getFieldValue($field, $invoice);
        $description = $field['description'] ?? '';
        
        $required_attr = $required ? 'required' : '';
        $required_class = $required ? 'required' : '';
        $description_html = $description ? '<p class="text-gray-500 mt-1 text-xs">' . esc_html($description) . '</p>' : '';
        
        return sprintf(
            '<div class="%s">
                <label for="%s" class="block text-sm font-medium text-gray-700 %s">%s</label>
                <div class="mt-1">
                    <input type="date" 
                           id="%s" 
                           name="%s" 
                           value="%s" 
                           placeholder="%s" 
                           class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm %s" 
                           %s>
                    %s
                </div>
            </div>',
            esc_attr($grid_cols),
            esc_attr($id),
            esc_attr($required_class),
            esc_html($label),
            esc_attr($id),
            esc_attr($name),
            esc_attr($value),
            esc_attr($placeholder),
            esc_attr($required_class),
            $required_attr,
            $description_html
        );
    }
    
    /**
     * Render a number field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderNumberField(array $field, $invoice = null): string {
        $id = $field['id'] ?? '';
        $name = $field['name'] ?? '';
        $label = $field['label'] ?? '';
        $placeholder = $field['placeholder'] ?? '';
        $required = $field['required'] ?? false;
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-6';
        $value = $this->getFieldValue($field, $invoice);
        $min = $field['min'] ?? '';
        $max = $field['max'] ?? '';
        $step = $field['step'] ?? '';
        $description = $field['description'] ?? '';
        
        $required_attr = $required ? 'required' : '';
        $required_class = $required ? 'required' : '';
        $min_attr = $min !== '' ? 'min="' . esc_attr($min) . '"' : '';
        $max_attr = $max !== '' ? 'max="' . esc_attr($max) . '"' : '';
        $step_attr = $step !== '' ? 'step="' . esc_attr($step) . '"' : '';
        $description_html = $description ? '<p class="text-gray-500 mt-1 text-xs">' . esc_html($description) . '</p>' : '';
        
        return sprintf(
            '<div class="%s">
                <label for="%s" class="block text-sm font-medium text-gray-700 %s">%s</label>
                <div class="mt-1">
                    <input type="number" 
                           id="%s" 
                           name="%s" 
                           value="%s" 
                           placeholder="%s" 
                           class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm %s" 
                           %s %s %s %s>
                    %s
                </div>
            </div>',
            esc_attr($grid_cols),
            esc_attr($id),
            esc_attr($required_class),
            esc_html($label),
            esc_attr($id),
            esc_attr($name),
            esc_attr($value),
            esc_attr($placeholder),
            esc_attr($required_class),
            $required_attr,
            $min_attr,
            $max_attr,
            $step_attr,
            $description_html
        );
    }
    
    /**
     * Render a select field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderSelectField(array $field, $invoice = null): string {
        $id = $field['id'] ?? $field['name'] ?? '';
        $name = $field['name'] ?? '';
        $label = $field['label'] ?? '';
        $required = $field['required'] ?? false;
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-6';
        $value = $this->getFieldValue($field, $invoice);
        $options = $field['options'] ?? [];
        $description = $field['description'] ?? '';
        
        $required_attr = $required ? 'required' : '';
        $required_class = $required ? 'required' : '';
        $description_html = $description ? '<p class="text-gray-500 mt-1 text-xs">' . esc_html($description) . '</p>' : '';
        
        $options_html = '';
        foreach ($options as $option_value => $option_label) {
            $selected = ($value == $option_value) ? 'selected' : '';
            $options_html .= sprintf(
                '<option value="%s" %s>%s</option>',
                esc_attr($option_value),
                $selected,
                esc_html($option_label)
            );
        }
        
        return sprintf(
            '<div class="%s">
                <label for="%s" class="block text-sm font-medium text-gray-700 %s">%s</label>
                <div class="mt-1">
                    <select id="%s" 
                            name="%s" 
                            class="mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md %s" 
                            %s>
                        %s
                    </select>
                    %s
                </div>
            </div>',
            esc_attr($grid_cols),
            esc_attr($id),
            esc_attr($required_class),
            esc_html($label),
            esc_attr($id),
            esc_attr($name),
            esc_attr($required_class),
            $required_attr,
            $options_html,
            $description_html
        );
    }
    
    /**
     * Render a textarea field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderTextareaField(array $field, $invoice = null): string {
        $id = $field['id'] ?? '';
        $name = $field['name'] ?? '';
        $label = $field['label'] ?? '';
        $placeholder = $field['placeholder'] ?? '';
        $required = $field['required'] ?? false;
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-6';
        $value = $this->getFieldValue($field, $invoice);
        $rows = $field['rows'] ?? 3;
        $description = $field['description'] ?? '';
        $required_attr = $required ? 'required' : '';
        $required_class = $required ? 'required' : '';
        $description_html = $description ? '<p class="text-gray-500 mt-1 text-xs">' . esc_html($description) . '</p>' : '';
        
        return sprintf(
            '<div class="%s">
                <label for="%s" class="block text-sm font-medium text-gray-700 %s">%s</label>
                <div class="mt-1">
                    <textarea id="%s" 
                              name="%s" 
                              rows="%s" 
                              placeholder="%s" 
                              class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm %s" 
                              %s>%s</textarea>
                    %s
                </div>
            </div>',
            esc_attr($grid_cols),
            esc_attr($id),
            esc_attr($required_class),
            esc_html($label),
            esc_attr($id),
            esc_attr($name),
            esc_attr($rows),
            esc_attr($placeholder),
            esc_attr($required_class),
            $required_attr,
            wp_kses_post($value),
            $description_html
        );
    }
    
    /**
     * Render a checkbox field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderCheckboxField(array $field, $invoice = null): string {
        $id = $field['id'] ?? $field['name'] ?? '';
        $name = $field['name'] ?? '';
        $label = $field['label'] ?? '';
        $description = $field['description'] ?? '';
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-6';
        $value = $this->getFieldValue($field, $invoice);
        $checked = $value ? 'checked' : '';
        
        $description_html = $description ? '<p class="text-gray-500 mt-1 text-xs">' . esc_html($description) . '</p>' : '';
        
        return sprintf(
            '<div class="%s">
                <div class="flex items-start">
                    <div class="flex items-center h-5">
                        <input type="checkbox" 
                               id="%s" 
                               name="%s" 
                               value="1" 
                               class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" 
                               %s>
                    </div>
                    <div class="ml-3 text-sm">
                        <label for="%s" class="font-medium text-gray-700">%s</label>
                        %s
                    </div>
                </div>
            </div>',
            esc_attr($grid_cols),
            esc_attr($id),
            esc_attr($name),
            $checked,
            esc_attr($id),
            esc_html($label),
            $description_html
        );
    }
    
    /**
     * Render a notice field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderNoticeField(array $field, $invoice = null): string {
        $label = $field['label'] ?? '';
        $description = $field['description'] ?? '';
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-6';
        $notice_type = $field['notice_type'] ?? 'info';
        
        // Set notice colors based on type
        $notice_classes = [
            'info' => 'bg-blue-50 border-blue-200 text-blue-700',
            'warning' => 'bg-yellow-50 border-yellow-200 text-yellow-700',
            'error' => 'bg-red-50 border-red-200 text-red-700',
            'success' => 'bg-green-50 border-green-200 text-green-700'
        ];
        
        $notice_class = $notice_classes[$notice_type] ?? $notice_classes['info'];
        
        return sprintf(
            '<div class="%s">
                <div class="p-4 border rounded-md %s">
                    <div class="flex">
                        <div class="flex-shrink-0">
                            <i class="fas fa-exclamation-triangle text-yellow-400"></i>
                        </div>
                        <div class="ml-3">
                            <h3 class="text-sm font-medium">%s</h3>
                            <div class="mt-2 text-sm">
                                <p>%s</p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>',
            esc_attr($grid_cols),
            esc_attr($notice_class),
            esc_html($label),
            esc_html($description)
        );
    }
    
    /**
     * Render a payment gateways field
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Rendered HTML
     */
    private function renderPaymentGatewaysField(array $field, $invoice = null): string {
        $id = $field['id'] ?? '';
        $name = $field['name'] ?? '';
        $label = $field['label'] ?? '';
        $description = $field['description'] ?? '';
        $grid_cols = $field['grid_cols'] ?? 'sm:col-span-12';
        $value = $this->getFieldValue($field, $invoice);
        
        // Get enabled payment gateways from the main plugin instance
        $payment_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
        $enabled_gateways = $payment_manager->getEnabledGateways();
        
        // Convert saved value to array if it's a string
        $selected_gateways = [];
        $has_custom_selection = false;
        if (!empty($value)) {
            if (is_string($value)) {
                $selected_gateways = explode(',', $value);
            } elseif (is_array($value)) {
                $selected_gateways = $value;
            }
            $has_custom_selection = !empty($selected_gateways);
        }
        
        // Check if toggle state is enabled (either from selected gateways or toggle state field)
        $toggle_enabled = $has_custom_selection;
        if ($invoice && method_exists($invoice, 'getPaymentGatewaysToggleState')) {
            $toggle_state = $invoice->getPaymentGatewaysToggleState();
            if ($toggle_state === '1') {
                $toggle_enabled = true;
            }
        }
        
        // Section heading with icon (fixed size) - will be shown/hidden via JavaScript
        $section_heading = '<div id="' . esc_attr($id) . '_heading" class="section-heading hidden"><svg class="gateway-icon text-indigo-500" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="10" rx="2" stroke="currentColor" stroke-width="2" fill="none"/><path d="M2 11h20" stroke="currentColor" stroke-width="2"/></svg><span class="text-lg font-semibold text-gray-800">' . esc_html($label) . '</span></div>';
        
        $description_html = '';
        if ($description) {
            $description_html = '<p class="text-gray-500 mt-1 text-xs">' . esc_html($description) . '</p>';
        }
        
        $gateways_html = '';
        if (empty($enabled_gateways)) {
            $gateways_html = '<div class="no-gateways-notice">' . __('No payment gateways are currently enabled. Please enable gateways in the plugin settings.', 'easy-invoice') . '</div>';
        } else {
            // Show current status only when toggle is on and gateways are selected
            if ($has_custom_selection) {
                $gateway_names = array_map(function($gateway) {
                    return $gateway->getTitle();
                }, $enabled_gateways);
                $gateways_html .= '<div class="status-box">';
                $gateways_html .= '<svg class="gateway-icon text-yellow-500" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 17v.01"/><path d="M12 13v-4"/><circle cx="12" cy="12" r="10"/></svg>';
                $selected_names = array_map(function($gateway) use ($enabled_gateways) {
                    return $enabled_gateways[$gateway]->getTitle();
                }, $selected_gateways);
                $gateways_html .= '<div class="status-content"><span class="main"><strong>' . __('Restricted to:', 'easy-invoice') . '</strong> ' . esc_html(implode(', ', $selected_names)) . '</span></div>';
                $gateways_html .= '</div>';
            }
            
            // Switch-style toggle for custom selection - improved visibility and design
            $gateways_html .= '<div class="toggle-container">';
            $gateways_html .= '<label class="toggle-row">';
            $gateways_html .= '<span class="toggle-label">' . __('Choose specific payment methods', 'easy-invoice') . '</span>';
            $gateways_html .= '<div class="toggle-switch">';
            $gateways_html .= '<input type="checkbox" id="payment_gateways_toggle" class="toggle-checkbox" ' . ($toggle_enabled ? 'checked' : '') . '/>';
            $gateways_html .= '<span class="toggle-slider"></span>';
            $gateways_html .= '</div>';
            $gateways_html .= '<span class="toggle-help" title="' . esc_attr(__('Enable to select which payment methods to offer for this invoice', 'easy-invoice')) . '">?</span>';
            $gateways_html .= '</label>';
            $gateways_html .= '<div class="toggle-description">' . __('By default, all payment methods are available. Enable this option to select specific methods - unchecked items will not appear during payment.', 'easy-invoice') . '</div>';
            $gateways_html .= '</div>';

            // Hidden field for selected gateways
            $initial_gateways_value = $has_custom_selection ? implode(',', $selected_gateways) : '';
            $gateways_html .= '<input type="hidden" name="payment_gateways_hidden" value="' . esc_attr($initial_gateways_value) . '" id="payment_gateways_hidden">';
            
            // Gateway cards (no icon, minimal, modern) - only show when toggle is enabled
            $gateways_html .= '<div id="' . esc_attr($id) . '_selection" class="gateway-cards hidden">';
            foreach ($enabled_gateways as $gateway_id => $gateway) {
                $checked = in_array($gateway_id, $selected_gateways) ? 'checked' : '';
                $is_checked = in_array($gateway_id, $selected_gateways);
                $gateway_title = $gateway->getTitle();
                $gateway_description = $gateway->getDescription();
                $is_available = $gateway->isAvailable();
                $availability_badge = $is_available ? '' : '<span class="badge">' . __('Configuration required', 'easy-invoice') . '</span>';
                $card_classes = 'gateway-card';
                if ($is_checked) {
                    $card_classes .= ' checked';
                }
                if (!$is_available) {
                    $card_classes .= ' opacity-60';
                }
                $gateways_html .= sprintf(
                    '<label class="%s" onclick="toggleGatewayCard(this)">
                        <input type="checkbox" id="%s_%s" name="%s[]" value="%s" %s>
                        <span class="custom-checkbox">'
                        . '<svg viewBox="0 0 20 20"><polyline points="4 11 8 15 16 6" fill="none" stroke="currentColor" stroke-width="2"/></svg>'
                        . '</span>' .
                        '<div class="gateway-card-content">'
                        . '<span class="gateway-title">%s</span>'
                        . '%s'
                        . '<span class="gateway-desc">%s</span>'
                        . '</div>' .
                    '</label>',
                    esc_attr($card_classes),
                    esc_attr($id),
                    esc_attr($gateway_id),
                    esc_attr($name),
                    esc_attr($gateway_id),
                    $checked,
                    esc_html($gateway_title),
                    $availability_badge,
                    esc_html($gateway_description)
                );
            }
            $gateways_html .= '</div>';
            
            // Add JavaScript to handle toggle and card clicks
            $gateways_html .= '<script>
                document.addEventListener("DOMContentLoaded", function() {
                    const toggle = document.getElementById("payment_gateways_toggle");
                    const selection = document.getElementById("' . esc_js($id) . '_selection");
                    const heading = document.getElementById("' . esc_js($id) . '_heading");
                    const toggleStateField = document.getElementById("payment_gateways_toggle_state");
                    const gatewaysHiddenField = document.getElementById("payment_gateways_hidden");
                    const checkboxes = selection.querySelectorAll("input[type=checkbox]");

                    function updateGatewaysHidden() {
                        const selectedValues = [];
                        checkboxes.forEach(function(checkbox) {
                            if (checkbox.checked) {
                                selectedValues.push(checkbox.value);
                            }
                        });
                        gatewaysHiddenField.value = selectedValues.join(",");
                    }

                    // Toggle functionality
                    toggle.addEventListener("change", function() {
                        if (this.checked) {
                            heading.classList.remove("hidden");
                            selection.classList.remove("hidden");
                            checkboxes.forEach(function(checkbox) {
                                checkbox.checked = true;
                                checkbox.disabled = false;
                                checkbox.closest(".gateway-card").classList.add("checked");
                            });
                            toggleStateField.value = "1";
                        } else {
                            heading.classList.add("hidden");
                            selection.classList.add("hidden");
                            checkboxes.forEach(function(checkbox) {
                                checkbox.checked = false;
                                checkbox.disabled = true;
                                checkbox.closest(".gateway-card").classList.remove("checked");
                            });
                            toggleStateField.value = "0";
                        }
                        updateGatewaysHidden();
                    });

                    // On load, always disable checkboxes initially
                    checkboxes.forEach(function(checkbox) {
                        checkbox.disabled = true;
                    });

                    // If toggle is already checked (from saved data), enable the cards and heading
                    if (toggle.checked) {
                        heading.classList.remove("hidden");
                        selection.classList.remove("hidden");
                        checkboxes.forEach(function(checkbox) {
                            checkbox.disabled = false;
                        });
                        // Restore saved gateway selections instead of checking all
                        const savedGateways = gatewaysHiddenField.value;
                        if (savedGateways) {
                            const savedGatewayArray = savedGateways.split(",");
                            checkboxes.forEach(function(checkbox) {
                                if (savedGatewayArray.includes(checkbox.value)) {
                                    checkbox.checked = true;
                                    checkbox.closest(".gateway-card").classList.add("checked");
                                } else {
                                    checkbox.checked = false;
                                    checkbox.closest(".gateway-card").classList.remove("checked");
                                }
                            });
                        }
                    }
                    updateGatewaysHidden();

                    // Handle card clicks
                    window.toggleGatewayCard = function(cardElement) {
                        const checkbox = cardElement.querySelector("input[type=checkbox]");
                        if (checkbox && !checkbox.disabled) {
                            checkbox.checked = !checkbox.checked;
                            if (checkbox.checked) {
                                cardElement.classList.add("checked");
                            } else {
                                cardElement.classList.remove("checked");
                            }
                            updateGatewaysHidden();
                        }
                    };

                    // Handle form submission to ensure proper data
                    const form = toggle.closest("form");
                    if (form) {
                        form.addEventListener("submit", function() {
                            if (!toggle.checked) {
                                checkboxes.forEach(function(checkbox) {
                                    checkbox.checked = false;
                                    checkbox.disabled = true;
                                });
                                toggleStateField.value = "0";
                            } else {
                                checkboxes.forEach(function(checkbox) {
                                    checkbox.disabled = false;
                                });
                                toggleStateField.value = "1";
                            }
                            updateGatewaysHidden();
                        });
                    }
                });
            </script>';
        }
        
        return sprintf(
            '<div class="%s">
                %s
                <div class="mt-3">
                    %s
                    %s
                </div>
            </div>',
            esc_attr($grid_cols),
            $section_heading,
            $description_html,
            $gateways_html
        );
    }
    
    /**
     * Get field value from invoice or default
     *
     * @since 1.0.0
     * @param array $field Field configuration
     * @param mixed $invoice Invoice object or null
     * @return string Field value
     */
    private function getFieldValue(array $field, $invoice = null): string {
        $name = $field['name'] ?? '';
        $default_value = $field['default_value'] ?? $field['value'] ?? '';

        // Handle callable default values
        if (is_callable($default_value)) {
            $default_value = $default_value();
        }

        // Allow Pro plugins to populate field values from meta data
        $field = apply_filters('easy_invoice_populate_field_values', $field, $invoice);

        if (is_array($invoice)) {
            // Handle different array structures
            if (isset($invoice[$name])) {
                $value = $invoice[$name];
            } elseif (isset($invoice['default_values'][$name])) {
                $value = $invoice['default_values'][$name];
            } else {
                $value = $default_value;
            }
        } else {
            if ($invoice) {
                // First check if the field has a populated value from the hook
                if (isset($field['value']) && $field['value'] !== null && $field['value'] !== '') {
                    $value = $field['value'];
                } else {
                    // Use dynamic property access (magic __get method)
                    if (method_exists($invoice, '__get') || property_exists($invoice, $name)) {
                        $value = $invoice->$name;
                    } else {
                        // Fallback to dynamic getter method
                $getter_method = 'get' . easy_invoice_str_replace(' ', '', ucwords(easy_invoice_str_replace(['-', '_'], ' ', $name)));
                if (method_exists($invoice, $getter_method)) {
                    $value = $invoice->$getter_method();
                } else {
                    $value = $default_value;
                        }
                }

                // Special handling for currency fields - use raw values for form display
                if ($name === 'currency_code') {
                    if (method_exists($invoice, 'getRawCurrencyCode')) {
                        return $invoice->getRawCurrencyCode();
                    }
                    return $value ?? 'global';
                }
                
                if ($name === 'currency_position') {
                    if (method_exists($invoice, 'getRawCurrencyPosition')) {
                        return $invoice->getRawCurrencyPosition();
                    }
                    return $value ?? 'global';
                }

                // Special handling for payment_gateways field
                if ($name === 'payment_gateways') {
                    if (is_array($value)) {
                        return implode(',', $value);
                    }
                    return (string) $value;
                }

                if (empty($value) && $value !== '0') {
                    $value = $default_value;
                    }
                }
            } else {
                $value = $default_value;
            }
        }
        
        return (string) ($value ?? '');
    }
} 
```
