# easy-invoice/2.2.0/includes/Admin/EasyInvoiceAjax.php

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

- Page: https://pluginprobe.com/plugins/easy-invoice/2.2.0/code/includes/Admin/EasyInvoiceAjax.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.2.0/raw/includes/Admin/EasyInvoiceAjax.php
- Modified: 2026-04-29T02:55:08+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/Admin/EasyInvoiceAjax.php#L10-L20`.

```php
<?php
/**
 * EasyInvoice AJAX Class
 *
 * @package Easy_Invoice
 * @subpackage Admin
 */

namespace EasyInvoice\Admin;

use EasyInvoice\Models\Invoice;
use EasyInvoice\Models\InvoiceItem;
use EasyInvoice\Providers\InvoiceServiceProvider;
use EasyInvoice\Providers\ClientServiceProvider;
use EasyInvoice\Constants\ClientFields;

/**
 * EasyInvoiceAjax Class
 *
 * Handles all AJAX functionality for the plugin.
 */
class EasyInvoiceAjax {
    /**
     * Initialize AJAX handlers
     */
    public function init() {
        // Invoice actions
        //add_action('wp_ajax_easy_invoice_save', array($this, 'saveInvoice'));
        add_action('wp_ajax_easy_invoice_delete', array($this, 'deleteInvoice'));
        add_action('wp_ajax_easy_invoice_get', array($this, 'getInvoice'));
        add_action('wp_ajax_easy_invoice_save_invoice', array($this, 'saveInvoice'));
        add_action('wp_ajax_easy_invoice_save_and_send_invoice', array($this, 'saveAndSendInvoice'));

        // Template actions



        // Document and email actions
        add_action('wp_ajax_easy_invoice_download_pdf', array($this, 'downloadPdf'));
        add_action('wp_ajax_easy_invoice_send_email', array($this, 'sendInvoiceEmail'));

        // Single page actions (for public access)
        add_action('wp_ajax_easy_invoice_download_invoice_pdf', array($this, 'downloadInvoicePdf'));
        add_action('wp_ajax_easy_invoice_send_invoice_email', array($this, 'sendInvoiceEmailPublic'));
        add_action('wp_ajax_easy_invoice_send_quote_email', array($this, 'sendQuoteEmailPublic'));
        add_action('wp_ajax_nopriv_easy_invoice_download_invoice_pdf', array($this, 'downloadInvoicePdf'));
        add_action('wp_ajax_nopriv_easy_invoice_send_invoice_email', array($this, 'sendInvoiceEmailPublic'));
        add_action('wp_ajax_nopriv_easy_invoice_send_quote_email', array($this, 'sendQuoteEmailPublic'));

        // PDF generation actions
        add_action('wp_ajax_easy_invoice_generate_pdf', array($this, 'generateInvoicePdf'));
        add_action('wp_ajax_easy_invoice_generate_quote_pdf', array($this, 'generateQuotePdf'));

        // Additional CSS actions
        add_action('wp_ajax_save_additional_css', array($this, 'saveAdditionalCSS'));
        add_action('wp_ajax_nopriv_easy_invoice_generate_pdf', array($this, 'generateInvoicePdf'));
        add_action('wp_ajax_nopriv_easy_invoice_generate_quote_pdf', array($this, 'generateQuotePdf'));

        // Quote document actions
        add_action('wp_ajax_easy_invoice_download_quote_pdf', array($this, 'downloadQuotePdf'));
        add_action('wp_ajax_easy_invoice_save_quote', array($this, 'saveQuote'));

        // Client actions
        add_action('wp_ajax_easy_invoice_save_client', array($this, 'saveClient'));
        add_action('wp_ajax_easy_invoice_delete_client', array($this, 'deleteClient'));
        add_action('wp_ajax_easy_invoice_get_client', array($this, 'getClient'));
        add_action('wp_ajax_easy_invoice_add_client', array($this, 'addClient'));
        add_action('wp_ajax_easy_invoice_update_client', array($this, 'updateClient'));
        add_action('wp_ajax_easy_invoice_check_email_exists', array($this, 'checkEmailExists'));
        add_action('wp_ajax_easy_invoice_generate_password', array($this, 'generatePassword'));
        add_action('wp_ajax_easy_invoice_search_clients', array($this, 'searchClients'));
        add_action('wp_ajax_easy_invoice_update_invoices_data', array($this, 'updateInvoicesData'));
    }

    /**
     * Save invoice
     */
    public function saveInvoice() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        // Get the raw invoice data from the form
        $raw_invoice_data = isset($_POST['invoice_data']) ? $_POST['invoice_data'] : $_POST;

        // Remove non-invoice fields
        unset($raw_invoice_data['action']);
        unset($raw_invoice_data['nonce']);

        // Process the invoice data
        $invoice_form_manager = new \EasyInvoice\Forms\Invoice\InvoiceFormManager();
        $invoice_data = $invoice_form_manager->processFormData($raw_invoice_data);

        if (!empty($invoice_data['errors'])) {
            wp_send_json_error([
                'message' => 'Validation failed',
                'errors' => $invoice_data['errors']
            ]);
        }

        // Handle items separately - process the natural form submission format
        if (isset($raw_invoice_data['items']) && is_array($raw_invoice_data['items'])) {
            // Form submits items as items[0][title], items[0][description], etc.
            // Convert to array of item objects for processing
            $items_array = [];
            foreach ($raw_invoice_data['items'] as $index => $item_data) {
                if (is_array($item_data)) {
                    $items_array[] = $item_data;
                }
            }

            // Process items using the dynamic field system
            $invoice_data['data']['items'] = $invoice_form_manager->processItemsData($items_array);
        }

        // Handle special fields that might not be in the form definition
        if (isset($raw_invoice_data['invoice_id'])) {
            $invoice_data['data']['invoice_id'] = intval($raw_invoice_data['invoice_id']);
        }

        if (isset($raw_invoice_data['client_id'])) {
            $invoice_data['data']['client_id'] = intval($raw_invoice_data['client_id']);
        }



        $invoice_id = isset($invoice_data['data']['invoice_id']) ? intval($invoice_data['data']['invoice_id']) : 0;

        $repository = InvoiceServiceProvider::getInvoiceRepository();

        if ($invoice_id > 0) {
            // Update existing invoice - preserve existing invoice number
            unset($invoice_data['data']['invoice_number']);
            unset($invoice_data['data']['number']);

            $invoice = $repository->update($invoice_id, $invoice_data['data']);

            if (!$invoice) {

                $this->sendError(__('Failed to update invoice', 'easy-invoice'));
            }

            // Use FormProcessor to save form data to database
            $form_processor = new \EasyInvoice\Forms\FormProcessor();
            $all_fields = $invoice_form_manager->getAllFields();
            $form_processor->saveFormDataToDatabase($invoice_data['data'], $all_fields, $invoice);

            $message = __('Invoice updated successfully', 'easy-invoice');
        } else {
            // Create new invoice - allow auto-generated invoice number to be saved
            // The invoice number will be auto-generated by the form and included in the data

            $invoice = $repository->create($invoice_data['data']);


            if (!$invoice) {
                $this->sendError(__('Failed to create invoice', 'easy-invoice'));
            }

            // Use FormProcessor to save form data to database
            $form_processor = new \EasyInvoice\Forms\FormProcessor();
            $all_fields = $invoice_form_manager->getAllFields();
            $form_processor->saveFormDataToDatabase($invoice_data['data'], $all_fields, $invoice);

            $invoice_id = $invoice->getId();
            $message = __('Invoice created successfully', 'easy-invoice');
        }

        // Handle items
        if (isset($invoice_data['data']['items']) && is_array($invoice_data['data']['items'])) {
            $invoice->setItems($invoice_data['data']['items']);
        }

		$invoice_template = get_post_meta($invoice_id, '_easy_invoice_invoice_template', true);

		$invoice_template = $invoice_template=='' ? 'standard': $invoice_template;

		update_option('easy_invoice_last_invoice_template',$invoice_template );

        // Prepare response data
        $response_data = array(
            'invoice_id' => $invoice_id,
            'invoice' => $invoice->toArray(),
            'toast' => array(
                'type' => 'success',
                'message' => $message,
                'options' => array('duration' => 4000)
            )
        );

        // Include client data if invoice has a client
        if ($invoice->getClientId()) {
            $client_repository = ClientServiceProvider::getClientRepository();
            $client = $client_repository->find($invoice->getClientId());
            if ($client) {
                $response_data['client'] = array(
                    'id' => $client->getId(),
                    'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
                    'email' => $client->getEmail() ?: '',
                    'phone' => $client->getExtraInfo() ?: '',
                    'company' => $client->getBusinessClientName() ?: '',
                    'address' => $client->getAddress() ?: '',
                    'website' => $client->getWebsite() ?: '',
                );
            }
        }

        wp_send_json_success($response_data);
    }

    /**
     * Save and send invoice
     */
    public function saveAndSendInvoice() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        // First save the invoice
        $this->saveInvoice();

        // If we get here, the invoice was saved successfully
        // Now send the invoice via email
        $invoice_id = isset($_POST['invoice_data']['invoice_id']) ? intval($_POST['invoice_data']['invoice_id']) : 0;

        if ($invoice_id > 0) {
            // Send the invoice via email
            $result = $this->sendInvoiceEmail($invoice_id);

            if ($result['success']) {
                $this->sendSuccess(array(
                    'message' => __('Invoice saved and sent successfully', 'easy-invoice'),
                    'invoice_id' => $invoice_id
                ));
            } else {
                $this->sendError($result['message']);
            }
        } else {
            $this->sendError(__('Invalid invoice ID for sending', 'easy-invoice'));
        }
    }

    /**
     * Delete invoice
     */
    public function deleteInvoice() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;

        if ($invoice_id <= 0) {
            $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
        }

        $repository = InvoiceServiceProvider::getInvoiceRepository();
        $result = $repository->delete($invoice_id);

        if (!$result) {
            $this->sendError(__('Failed to delete invoice', 'easy-invoice'));
        }

        $this->sendSuccess(array(
            'message' => __('Invoice deleted successfully', 'easy-invoice'),
            'invoice_id' => $invoice_id,
        ));
    }

    /**
     * Get invoice
     */
    public function getInvoice() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        $invoice_id = isset($_REQUEST['invoice_id']) ? intval($_REQUEST['invoice_id']) : 0;

        if ($invoice_id <= 0) {
            $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
        }

        $repository = InvoiceServiceProvider::getInvoiceRepository();
        $invoice = $repository->find($invoice_id);

        if (!$invoice) {
            $this->sendError(__('Invoice not found', 'easy-invoice'));
        }

        $this->sendSuccess(array(
            'invoice' => $invoice->toArray(),
        ));
    }

    /**
     * Save client
     */
    public function saveClient() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        $client_id = isset($_POST['client_id']) ? intval($_POST['client_id']) : 0;
        $client_data = isset($_POST['client_data']) ? $this->sanitizeData($_POST['client_data']) : array();

        if (empty($client_data)) {
            $this->sendError(__('Invalid client data', 'easy-invoice'));
        }

        $repository = ClientServiceProvider::getClientRepository();

        if ($client_id > 0) {
            // Update existing client
            $client = $repository->update($client_id, $client_data);

            if (!$client) {
                $this->sendError(__('Failed to update client', 'easy-invoice'));
            }

            $message = __('Client updated successfully', 'easy-invoice');
        } else {
            // Create new client
            $client = $repository->create($client_data);

            if (!$client) {
                $this->sendError(__('Failed to create client', 'easy-invoice'));
            }

            $client_id = $client->getId();
            $message = __('Client created successfully', 'easy-invoice');
        }

        $this->sendSuccess(array(
            'message' => $message,
            'client_id' => $client_id,
            'client' => $client->toArray(),
        ));
    }

    /**
     * Delete client
     */
    public function deleteClient() {
        try {
            $this->verifyNonce('easy_invoice_nonce');

            if (!current_user_can('manage_options')) {
                $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
            }

            $client_id = isset($_POST['client_id']) ? intval($_POST['client_id']) : 0;
            $delete_associated_documents = isset($_POST['delete_associated_documents']) ? (bool)$_POST['delete_associated_documents'] : false;

            if ($client_id <= 0) {
                $this->sendError(__('Invalid client ID', 'easy-invoice'));
            }

            // Check if the user exists and is not an administrator
            $user = get_user_by('ID', $client_id);
            if (!$user) {
                $this->sendError(__('User not found', 'easy-invoice'));
            }

            if (in_array('administrator', $user->roles)) {
                $this->sendError(__('Cannot delete administrator accounts', 'easy-invoice'));
            }

            global $wpdb;

            // Get counts of associated documents
            $invoice_count = $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_easy_invoice_client_id' AND meta_value = %d",
                $client_id
            ));

            $quote_count = $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_easy_invoice_quote_client_id' AND meta_value = %d",
                $client_id
            ));

            $payment_count = $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_easy_payment_client_id' AND meta_value = %d",
                $client_id
            ));

            $total_documents = $invoice_count + $quote_count + $payment_count;

            if ($delete_associated_documents) {
                // Delete all associated documents
                $this->log(sprintf('Deleting client %d with all associated documents (%d invoices, %d quotes, %d payments)',
                    $client_id, $invoice_count, $quote_count, $payment_count));

                // Delete invoices
                if ($invoice_count > 0) {
                    $invoices = $wpdb->get_col($wpdb->prepare(
                        "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_easy_invoice_client_id' AND meta_value = %d",
                        $client_id
                    ));
                    foreach ($invoices as $invoice_id) {
                        wp_delete_post($invoice_id, true);
                    }
                }

                // Delete quotes
                if ($quote_count > 0) {
                    $quotes = $wpdb->get_col($wpdb->prepare(
                        "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_easy_invoice_quote_client_id' AND meta_value = %d",
                        $client_id
                    ));
                    foreach ($quotes as $quote_id) {
                        wp_delete_post($quote_id, true);
                    }
                }

                // Delete payments
                if ($payment_count > 0) {
                    $payments = $wpdb->get_col($wpdb->prepare(
                        "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_easy_invoice_payment_client_id' AND meta_value = %d",
                        $client_id
                    ));
                    foreach ($payments as $payment_id) {
                        wp_delete_post($payment_id, true);
                    }
                }

                $message = sprintf(__('Client and all associated documents (%d total) deleted successfully', 'easy-invoice'), $total_documents);
            } else {
                // Only remove client associations, preserve documents
                $this->log(sprintf('Removing client associations for client %d (%d invoices, %d quotes, %d payments)',
                    $client_id, $invoice_count, $quote_count, $payment_count));

                // Remove client associations from invoices
                if ($invoice_count > 0) {
                    $wpdb->delete(
                        $wpdb->postmeta,
                        ['meta_key' => '_easy_invoice_client_id', 'meta_value' => $client_id]
                    );
                }

                // Remove client associations from quotes
                if ($quote_count > 0) {
                    $wpdb->delete(
                        $wpdb->postmeta,
                        ['meta_key' => '_easy_invoice_quote_client_id', 'meta_value' => $client_id]
                    );
                }

                // Remove client associations from payments
                if ($payment_count > 0) {
                    $wpdb->delete(
                        $wpdb->postmeta,
                        ['meta_key' => '_easy_payment_client_id', 'meta_value' => $client_id]
                    );
                }

                $message = sprintf(__('Client deleted successfully. %d documents preserved but client associations removed.', 'easy-invoice'), $total_documents);
            }

            // Delete the WordPress user
            require_once(ABSPATH . 'wp-admin/includes/user.php');
            $result = wp_delete_user($client_id);

            if (!$result) {
                $this->sendError(__('Failed to delete client', 'easy-invoice'));
            }

            $this->sendSuccess(array(
                'message' => $message,
                'client_id' => $client_id,
                'documents_deleted' => $delete_associated_documents,
                'total_documents' => $total_documents
            ));

        } catch (\Exception $e) {
            $this->sendError($e->getMessage());
        }
    }

    /**
     * Get client
     */
    public function getClient() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        $client_id = isset($_REQUEST['client_id']) ? intval($_REQUEST['client_id']) : 0;

        if ($client_id <= 0) {
            $this->sendError(__('Invalid client ID', 'easy-invoice'));
        }

        $repository = ClientServiceProvider::getClientRepository();
        $client = $repository->find($client_id);

        if (!$client) {
            $this->sendError(__('Client not found', 'easy-invoice'));
        }

        $client_data = $client->toArray();

        // Return comprehensive client data in a unified format that works for both form population and display
        $this->sendSuccess(array(
            // Form population fields (for invoice-builder.js and invoice-form.js)
            'name' => $client_data['company_name'] ?? $client_data['contact_name'] ?? '',
            'email' => $client_data['email'] ?? '',
            'phone' => $client_data['phone'] ?? '',
            'company' => $client_data['company_name'] ?? '',
            'address' => $client_data['billing_address'] ?? '',
            'website' => $client_data['website'] ?? '',

            // Display fields (for client-manager.js)
            'business_client_name' => $client->getBusinessClientName(),
            'username' => $client->getUsername(),
            'extra_info' => $client->getExtraInfo(),
            'first_name' => $client->getFirstName(),
            'last_name' => $client->getLastName(),

            // Raw data for backward compatibility
            'client' => array(
                'name' => $client_data['company_name'] ?? $client_data['contact_name'] ?? '',
                'email' => $client_data['email'] ?? '',
                'phone' => $client_data['phone'] ?? '',
                'company' => $client_data['company_name'] ?? '',
                'address' => $client_data['billing_address'] ?? '',
                'website' => $client_data['website'] ?? '',
            )
        ));
    }

    /**
     * Verify nonce
     *
     * @param string $action The nonce action
     */
    private function verifyNonce($action) {
        // Check for _nonce (standard format) first
        if (isset($_REQUEST['_nonce']) && wp_verify_nonce($_REQUEST['_nonce'], $action)) {
            return;
        }

        // Also check for 'nonce' (client form format)
        if (isset($_REQUEST['nonce']) && wp_verify_nonce($_REQUEST['nonce'], $action)) {
            return;
        }

        // If we get here, neither nonce format was valid
        $this->sendError(__('Security check failed', 'easy-invoice'));
    }

    /**
     * Sanitize data
     *
     * @param array $data The data to sanitize
     * @return array The sanitized data
     */
    private function sanitizeData($data) {
        if (!is_array($data)) {
            return array();
        }

        $sanitized = array();

        // Define fields that should allow HTML (like textarea content)
        $html_fields = [
            'invoice_description', 'description', 'notes', 'terms',
            'internal_notes', 'customer_address'
        ];

        // Define numeric fields
        $numeric_fields = [
            'invoice_id', 'client_id', 'discount_value', 'tax_rate'
        ];

        foreach ($data as $key => $value) {
            if (is_array($value)) {
                $sanitized[$key] = $this->sanitizeData($value);
            } else if (in_array($key, $html_fields)) {
                // For HTML fields, use wp_kses to allow certain tags but prevent XSS
                $sanitized[$key] = wp_kses_post($value);
            } else if (in_array($key, $numeric_fields)) {
                // For numeric fields, ensure they're valid numbers
                $sanitized[$key] = is_numeric($value) ? $value : 0;
            } else {
                $sanitized[$key] = sanitize_text_field($value);
            }
        }

        return $sanitized;
    }

    /**
     * Send success response
     */
    private function sendSuccess($data = array()) {
        // Check if we should suppress global toast
        $suppress_toast = isset($_POST['suppress_global_toast']) && $_POST['suppress_global_toast'] === 'true';

        // Add toast notification if not already present and not suppressed
        if (!isset($data['toast']) && !$suppress_toast) {
            $message = isset($data['message']) ? $data['message'] : __('Operation completed successfully', 'easy-invoice');
            $data['toast'] = array(
                'type' => 'success',
                'message' => $message,
                'options' => array('duration' => 4000)
            );
        }

        // Remove toast data if suppressed
        if ($suppress_toast && isset($data['toast'])) {
            unset($data['toast']);
        }

        wp_send_json_success($data);
    }

    /**
     * Send error response
     */
    private function sendError($message, $data = array()) {
        // Add toast notification
        $data['toast'] = array(
            'type' => 'error',
            'message' => $message,
            'options' => array('duration' => 6000)
        );

        wp_send_json_error($data);
    }

    /**
     * Download invoice as PDF
     */
    public function downloadPdf() {
        // Verify nonce
            $this->verifyNonce('easy_invoice_nonce');

        // Check if user has required capability
        if (!current_user_can('edit_posts')) {
            $this->sendError(__('You do not have permission to download invoices', 'easy-invoice'));
            }

        // Get invoice ID
        $invoice_id = isset($_REQUEST['invoice_id']) ? intval($_REQUEST['invoice_id']) : 0;

        if (!$invoice_id) {
                $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
            }

        // Get invoice from repository
            $repository = InvoiceServiceProvider::getInvoiceRepository();
            $invoice = $repository->find($invoice_id);

            if (!$invoice) {
                $this->sendError(__('Invoice not found', 'easy-invoice'));
            }

        // Get invoice data for PDF generation
        $invoice_data = \EasyInvoice\Includes\Helpers\PdfHelper::getInvoiceDataForPdf($invoice);

        // Return success response with invoice data
                $this->sendSuccess(array(
            'message' => __('Invoice data retrieved successfully', 'easy-invoice'),
            'invoice_data' => $invoice_data
        ));
    }

    /**
     * Send invoice via email
     */
    public function sendInvoiceEmail() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        // Get invoice ID from POST data
        $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;

        if (!$invoice_id) {
            $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
        }

        $repository = InvoiceServiceProvider::getInvoiceRepository();
        $invoice = $repository->find($invoice_id);

        if (!$invoice) {
            $this->sendError(__('Invoice not found', 'easy-invoice'));
        }

        // Use EmailManager to send the email
        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
        $result = $email_manager->sendInvoiceEmail($invoice, 'new');

        if ($result['success']) {
            $this->sendSuccess(array(
                'message' => $result['message']
            ));
        } else {
            $this->sendError($result['message']);
        }
    }

    /**
     * Download quote as PDF
     */
    public function downloadQuotePdf() {
        // Verify nonce
        $this->verifyNonce('easy_invoice_nonce');

        // Check if user has required capability
        if (!current_user_can('edit_posts')) {
            $this->sendError(__('You do not have permission to download quotes', 'easy-invoice'));
        }

        // Get quote ID
        $quote_id = isset($_POST['quote_id']) ? intval($_POST['quote_id']) : 0;

        if (!$quote_id) {
            $this->sendError(__('Invalid quote ID', 'easy-invoice'));
        }

        // Get quote from repository
        $repository = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository();
        $quote = $repository->find($quote_id);

        if (!$quote) {
            $this->sendError(__('Quote not found', 'easy-invoice'));
        }

        // For now, return success response with quote data
        // PDF generation can be implemented later with actual PDF creation
        $this->sendSuccess(array(
            'message' => __('Quote data retrieved successfully', 'easy-invoice'),
            'quote_data' => $quote->toArray(),
            'download_url' => add_query_arg(array(
                'action' => 'easy_invoice_generate_quote_pdf',
                'quote_id' => $quote_id,
                'nonce' => wp_create_nonce('generate_quote_pdf')
            ), admin_url('admin-ajax.php'))
        ));
    }

    /**
     * Save quote
     */
    public function saveQuote() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        // Get the raw quote data from the form
        $raw_quote_data = isset($_POST['quote_data']) ? $_POST['quote_data'] : $_POST;

        // Remove non-quote fields
        unset($raw_quote_data['action']);
        unset($raw_quote_data['nonce']);

        // Process the quote data
        $quote_form_manager = new \EasyInvoice\Forms\Quote\QuoteFormManager();
        $quote_data = $quote_form_manager->processFormData($raw_quote_data);

        if (!empty($quote_data['errors'])) {
            wp_send_json_error([
                'message' => 'Validation failed',
                'errors' => $quote_data['errors']
            ]);
        }

        // Handle items separately - process the natural form submission format
        if (isset($raw_quote_data['items']) && is_array($raw_quote_data['items'])) {
            // Form submits items as items[0][title], items[0][description], etc.
            // Convert to array of item objects for processing
            $items_array = [];
            foreach ($raw_quote_data['items'] as $index => $item_data) {
                if (is_array($item_data)) {
                    $items_array[] = $item_data;
                }
            }

            // Process items using the dynamic field system
            $quote_data['data']['items'] = $quote_form_manager->processItemsData($items_array);
        }

        // Handle special fields that might not be in the form definition
        if (isset($raw_quote_data['quote_id'])) {
            $quote_data['data']['quote_id'] = intval($raw_quote_data['quote_id']);
        }

        if (isset($raw_quote_data['client_id'])) {
            $quote_data['data']['client_id'] = intval($raw_quote_data['client_id']);
        }



        $quote_id = isset($quote_data['data']['quote_id']) ? intval($quote_data['data']['quote_id']) : 0;

        $repository = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository();

        if ($quote_id > 0) {
            // Update existing quote - preserve existing quote number
            unset($quote_data['data']['quote_number']);
            unset($quote_data['data']['number']);

            // Get the existing quote first
            $quote = $repository->find($quote_id);

            if (!$quote) {
                $this->sendError(__('Failed to find quote for update', 'easy-invoice'));
            }

            // Use FormProcessor to save form data to database BEFORE repository update
            $form_processor = new \EasyInvoice\Forms\FormProcessor();
            $all_fields = $quote_form_manager->getAllFields();
            $form_processor->saveFormDataToDatabase($quote_data['data'], $all_fields, $quote);

            // Now update the quote with the processed data, passing the existing quote object
            $quote = $repository->update($quote_id, $quote_data['data'], $quote);

            if (!$quote) {
                $this->sendError(__('Failed to update quote', 'easy-invoice'));
            }

            $message = __('Quote updated successfully', 'easy-invoice');
        } else {
            // Create new quote - allow auto-generated quote number to be saved
            // The quote number will be auto-generated by the form and included in the data

            $quote = $repository->create($quote_data['data']);

            if (!$quote) {
                $this->sendError(__('Failed to create quote', 'easy-invoice'));
            }

            // Use FormProcessor to save form data to database
            $form_processor = new \EasyInvoice\Forms\FormProcessor();
            $all_fields = $quote_form_manager->getAllFields();
            $form_processor->saveFormDataToDatabase($quote_data['data'], $all_fields, $quote);

            $quote_id = $quote->getId();
            $message = __('Quote created successfully', 'easy-invoice');
        }

        // Handle items
        if (isset($quote_data['data']['items']) && is_array($quote_data['data']['items'])) {
            $quote->setItems($quote_data['data']['items']);
            // Save the quote to persist the items to database
            $quote->save();
        }

		$quote_template = get_post_meta($quote_id, '_easy_invoice_quote_quote_template', true);

		$quote_template = $quote_template=='' ? 'standard': $quote_template;

 		update_option('easy_invoice_last_quote_template',$quote_template );
        // Prepare response data
        $response_data = array(
            'quote_id' => $quote_id,
            'quote' => $quote->toArray(),
            'toast' => array(
                'type' => 'success',
                'message' => $message,
                'options' => array('duration' => 4000)
            )
        );

        // Include client data if quote has a client
        if ($quote->getClientId()) {
            $client_repository = ClientServiceProvider::getClientRepository();
            $client = $client_repository->find($quote->getClientId());
            if ($client) {
                $response_data['client'] = array(
                    'id' => $client->getId(),
                    'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
                    'email' => $client->getEmail() ?: '',
                    'phone' => $client->getExtraInfo() ?: '',
                    'company' => $client->getBusinessClientName() ?: '',
                    'address' => $client->getAddress() ?: '',
                    'website' => $client->getWebsite() ?: '',
                );
            }
        }


        $this->sendSuccess($response_data);
    }

    /**
     * Toggle a template as favorite
     */




    /**
     * Check if an email already exists for any client
     */
    public function checkEmailExists() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';

        if (empty($email)) {
            $this->sendSuccess(array('exists' => false));
        }

        $repository = ClientServiceProvider::getClientRepository();
        $existing_clients = $repository->findByEmail($email);

        $this->sendSuccess(array(
            'exists' => !empty($existing_clients),
            'count' => count($existing_clients)
        ));
    }

    /**
     * Generate a secure password.
     */
    public function generatePassword() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        $password = wp_generate_password(16, true, true);

        // Check if we should suppress global toast
        $suppress_toast = isset($_POST['suppress_global_toast']) && $_POST['suppress_global_toast'] === 'true';

        $this->sendSuccess(array(
            'password' => $password,
            'suppress_toast' => $suppress_toast
        ));
    }

    /**
     * Sanitize invoice items
     *
     * @param array $items Raw items data
     * @return array Sanitized items data
     */
    private function sanitizeItems(array $items): array {
        $sanitized_items = [];

        // Get field configuration for dynamic processing
        $form_manager = new \EasyInvoice\Forms\Invoice\InvoiceFormManager();
        $field_config = $form_manager->getItemFields();

        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }

            $sanitized_item = [];

            // Process each field dynamically based on configuration
            foreach ($field_config as $field) {
                $field_name = $field['name'] ?? '';
                $field_type = $field['type'] ?? 'text';
                $raw_value = $item[$field_name] ?? '';

                // Apply field-specific sanitization
                switch ($field_type) {
                    case 'text':
                        $sanitized_item[$field_name] = sanitize_text_field($raw_value);
                        break;
                    case 'textarea':
                        $sanitized_item[$field_name] = wp_kses_post($raw_value);
                        break;
                    case 'number':
                        $sanitized_item[$field_name] = is_numeric($raw_value) ? floatval($raw_value) : 0;
                        break;
                    case 'checkbox':
                        $sanitized_item[$field_name] = !empty($raw_value) ? true : false;
                        break;
                    default:
                        $sanitized_item[$field_name] = sanitize_text_field($raw_value);
                        break;
                }
            }

            // Handle legacy field names for backward compatibility
            if (isset($item['name']) && !isset($sanitized_item['title'])) {
                $sanitized_item['title'] = sanitize_text_field($item['name']);
            }
            if (isset($item['title']) && !isset($sanitized_item['title'])) {
                $sanitized_item['title'] = sanitize_text_field($item['title']);
            }

            // Only add items that have at least a title/name
            if (!empty($sanitized_item['title'])) {
                $sanitized_items[] = $sanitized_item;
            }
        }

        return $sanitized_items;
    }

    /**
     * Add a new client (specifically for the client form in templates/clients-page.php)
     */
    public function addClient() {

        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        // Check if required fields are present
        $required_fields = ['business_client_name', 'email', 'username'];
        foreach ($required_fields as $field) {
            if (!isset($_POST[$field]) || empty($_POST[$field])) {
                $this->sendError(__('Missing required field: ' . $field, 'easy-invoice'));
            }
        }

        // Prepare client data
        $client_data = [
            ClientFields::BUSINESS_CLIENT_NAME => sanitize_text_field($_POST['business_client_name']),
            ClientFields::EMAIL => sanitize_email($_POST['email']),
            ClientFields::USERNAME => sanitize_user($_POST['username']),
            ClientFields::PASSWORD => $_POST['password'],
            ClientFields::ADDRESS => sanitize_textarea_field($_POST['address']),
            ClientFields::EXTRA_INFO => sanitize_textarea_field($_POST['extra_info']),
            ClientFields::FIRST_NAME => sanitize_text_field($_POST['first_name']),
            ClientFields::LAST_NAME => sanitize_text_field($_POST['last_name']),
            ClientFields::WEBSITE => esc_url_raw($_POST['website']),
            ClientFields::PHONE => isset($_POST['phone']) ? sanitize_text_field($_POST['phone']) : '',
        ];



        // Basic validation
        if (empty($client_data[ClientFields::BUSINESS_CLIENT_NAME]) && (empty($client_data[ClientFields::FIRST_NAME]) || empty($client_data[ClientFields::LAST_NAME]))) {
            $this->sendError(__('Please provide a client name or first/last name.', 'easy-invoice'));
        }

        if (empty($client_data[ClientFields::EMAIL])) {
            $this->sendError(__('Email address is required', 'easy-invoice'));
        }

        $repository = ClientServiceProvider::getClientRepository();

        // Create new client
        $client = $repository->create($client_data);

        if (!$client) {
            $this->sendError(__('Failed to create client', 'easy-invoice'));
        }

        $client_id = $client->getId();

        $response_data = array(
            'message' => __('Client added successfully', 'easy-invoice'),
            'client_id' => $client_id,
            'client' => $client->toArray(),
        );

        $this->sendSuccess($response_data);
    }

    /**
     * Update client from the client edit form
     */
    public function updateClient() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        $client_id = isset($_POST['client_id']) ? intval($_POST['client_id']) : 0;

        if ($client_id <= 0) {
            $this->sendError(__('Invalid client ID', 'easy-invoice'));
        }

        // Prepare client data
        $client_data = [
            ClientFields::BUSINESS_CLIENT_NAME => sanitize_text_field($_POST['business_client_name']),
            ClientFields::EMAIL => sanitize_email($_POST['email']),
            ClientFields::USERNAME => sanitize_user($_POST['username']),
            ClientFields::PASSWORD => $_POST['password'], // Keep password as is, don't sanitize
            ClientFields::ADDRESS => sanitize_textarea_field($_POST['address']),
            ClientFields::PHONE => isset($_POST['phone']) ? sanitize_text_field($_POST['phone']) : '',
            ClientFields::EXTRA_INFO => sanitize_textarea_field($_POST['extra_info']),
            ClientFields::FIRST_NAME => sanitize_text_field($_POST['first_name']),
            ClientFields::LAST_NAME => sanitize_text_field($_POST['last_name']),
            ClientFields::WEBSITE => esc_url_raw($_POST['website'])
        ];

        // Remove empty values except password (password can be empty for updates)
        $client_data = array_filter($client_data, function($value, $key) {
            if ($key === ClientFields::PASSWORD) {
                return true; // Always include password field
            }
            return $value !== '';
        }, ARRAY_FILTER_USE_BOTH);

        if (empty($client_data)) {
            $this->sendError(__('No data provided to update.', 'easy-invoice'));
        }

        $repository = ClientServiceProvider::getClientRepository();

        // Update existing client
        $client = $repository->update($client_id, $client_data);

        if (!$client) {
            $this->sendError(__('Failed to update client', 'easy-invoice'));
        }

        $this->sendSuccess(array(
            'message' => __('Client updated successfully', 'easy-invoice'),
            'client_id' => $client_id,
            'client' => $client->toArray(),
        ));
    }

    /**
     * Update invoices data with missing client information and totals
     */
    public function updateInvoicesData() {
        $this->verifyNonce('easy_invoice_admin_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        $repository = InvoiceServiceProvider::getInvoiceRepository();
        $client_repository = ClientServiceProvider::getClientRepository();

        // Get all invoices
        $invoices = $repository->all();
        $updated_count = 0;

        foreach ($invoices as $invoice) {
            $updated = false;

            // Check if client data is missing
            $client_id = $invoice->getClientId();
            if ($client_id > 0) {
                $client = $client_repository->find($client_id);
                if ($client) {
                    // Update customer name if missing
                    $customer_name = $invoice->getCustomerName();
                    if (empty($customer_name)) {
                        $customer_name = $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName());
                        $invoice->setCustomerName($customer_name);
                        $updated = true;
                    }

                    // Update customer email if missing
                    $customer_email = $invoice->getCustomerEmail();
                    if (empty($customer_email)) {
                        $customer_email = $client->getEmail();
                        $invoice->setCustomerEmail($customer_email);
                        $updated = true;
                    }

                    // Update customer address if missing
                    $customer_address = $invoice->getCustomerAddress();
                    if (empty($customer_address)) {
                        $customer_address = $client->getAddress();
                        $invoice->setCustomerAddress($customer_address);
                        $updated = true;
                    }
                }
            }

            // Check if total is missing or zero
            $total = $invoice->getTotal();
            if (empty($total) || $total == 0) {
                // Recalculate total from items
                $items = $invoice->getItems();
                if (!empty($items)) {
                    $subtotal = 0;
                    foreach ($items as $item) {
                        if (method_exists($item, 'getAmount')) {
                            $subtotal += $item->getAmount();
                        } elseif (isset($item['amount'])) {
                            $subtotal += $item['amount'];
                        }
                    }

                    // Calculate discount and tax
                    $discount = $invoice->getDiscountAmount();
                    $tax = $invoice->getTaxAmount();

                    $total = $subtotal - $discount + $tax;

                    // Save the calculated total
                    $invoice->setMeta('_easy_invoice_total', $total);
                    $updated = true;
                }
            }

            if ($updated) {
                $updated_count++;
            }
        }

        $this->sendSuccess(array(
            'message' => sprintf(__('Updated %d invoices with missing data', 'easy-invoice'), $updated_count),
            'updated_count' => $updated_count
        ));
    }

    /**
     * Download invoice as PDF (public access)
     */
    public function downloadInvoicePdf() {
        // Verify nonce
        $this->verifyNonce('easy_invoice_nonce');

        // Get invoice ID
        $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;

        if (!$invoice_id) {
            $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
        }

        // Get invoice from repository (only published invoices for public access)
        $repository = InvoiceServiceProvider::getInvoiceRepository();

        // For admins, allow access to any invoice status
        if (current_user_can('manage_options')) {
            $invoice = $repository->find($invoice_id);
        } else {
            // For non-admins, only allow access to published invoices
            $invoice = $repository->findPublished($invoice_id);
        }

        if (!$invoice) {
            $this->sendError(__('Invoice not found', 'easy-invoice'));
        }

        // Get invoice data for PDF generation
        $invoice_data = \EasyInvoice\Includes\Helpers\PdfHelper::getInvoiceDataForPdf($invoice);

        // For now, return success response with invoice data
        // PDF generation can be implemented later with actual PDF creation
        $this->sendSuccess(array(
            'message' => __('Invoice data retrieved successfully', 'easy-invoice'),
            'invoice_data' => $invoice_data,
            'download_url' => add_query_arg(array(
                'action' => 'easy_invoice_generate_pdf',
                'invoice_id' => $invoice_id,
                'nonce' => wp_create_nonce('generate_pdf')
            ), admin_url('admin-ajax.php'))
        ));
    }

    /**
     * Send invoice via email (public access)
     */
    public function sendInvoiceEmailPublic() {
        $this->verifyNonce('easy_invoice_send_invoice_email');

        // Get invoice ID
        $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;

        if (!$invoice_id) {
            $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
        }

        // Get invoice from repository (only published invoices for public access)
        $repository = InvoiceServiceProvider::getInvoiceRepository();

        // For admins, allow access to any invoice status
        if (current_user_can('manage_options')) {
            $invoice = $repository->find($invoice_id);
        } else {
            // For non-admins, only allow access to published invoices
            $invoice = $repository->findPublished($invoice_id);
        }

        if (!$invoice) {
            $this->sendError(__('Invoice not found', 'easy-invoice'));
        }

        // Use EmailManager to send the email
        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
        $result = $email_manager->sendInvoiceEmail($invoice, 'new');

        if ($result['success']) {
            $this->sendSuccess(array(
                'message' => $result['message']
            ));
        } else {
            $this->sendError($result['message']);
        }
    }

    /**
     * Send quote via email (admin + public; guests only for published quotes).
     */
    public function sendQuoteEmailPublic() {
        $this->verifyNonce('easy_invoice_send_quote_email');

        $quote_id = isset($_POST['quote_id']) ? intval($_POST['quote_id']) : 0;

        if (!$quote_id) {
            $this->sendError(__('Invalid quote ID', 'easy-invoice'));
        }

        $repository = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository();

        if (current_user_can('manage_options')) {
            $quote = $repository->find($quote_id);
        } else {
            $quote = $repository->findPublished($quote_id);
        }

        if (!$quote) {
            $this->sendError(__('Quote not found', 'easy-invoice'));
        }

        $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
        $result = $email_manager->sendQuoteEmail($quote, 'new');

        if ($result['success']) {
            $quote_log_service = new \EasyInvoice\Services\QuoteLogService();
            $quote_log_service->logSent($quote_id, $quote->getCustomerEmail());

            $this->sendSuccess(array(
                'message' => $result['message'],
            ));
        } else {
            $this->sendError($result['message']);
        }
    }

    /**
     * Generate invoice PDF
     */
    public function generateInvoicePdf() {
        // Verify nonce
        $this->verifyNonce('generate_pdf');

        // Get invoice ID
        $invoice_id = isset($_REQUEST['invoice_id']) ? intval($_REQUEST['invoice_id']) : 0;

        if (!$invoice_id) {
            $this->sendError(__('Invalid invoice ID', 'easy-invoice'));
        }

        // Get invoice from repository
        $repository = InvoiceServiceProvider::getInvoiceRepository();

        // For admins, allow access to any invoice status
        if (current_user_can('manage_options')) {
            $invoice = $repository->find($invoice_id);
        } else {
            // For non-admins, only allow access to published invoices
            $invoice = $repository->findPublished($invoice_id);
        }

        if (!$invoice) {
            $this->sendError(__('Invoice not found', 'easy-invoice'));
        }

        // Redirect to the invoice single page with PDF generation
        $invoice_url = get_permalink($invoice_id);
        if ($invoice_url) {
            wp_redirect(add_query_arg('auto_download_pdf', '1', $invoice_url));
            exit;
        } else {
            $this->sendError(__('Could not generate invoice URL', 'easy-invoice'));
        }
    }

    /**
     * Generate quote PDF
     */
    public function generateQuotePdf() {
        // Verify nonce
        $this->verifyNonce('generate_quote_pdf');

        // Get quote ID
        $quote_id = isset($_REQUEST['quote_id']) ? intval($_REQUEST['quote_id']) : 0;

        if (!$quote_id) {
            $this->sendError(__('Invalid quote ID', 'easy-invoice'));
        }

        // Get quote from repository — mirror invoice PDF: only published quotes for non-admins (incl. nopriv).
        $repository = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository();
        if (current_user_can('manage_options')) {
            $quote = $repository->find($quote_id);
        } else {
            $quote = $repository->findPublished($quote_id);
        }

        if (!$quote) {
            $this->sendError(__('Quote not found', 'easy-invoice'));
        }

        // Redirect to the quote single page with PDF generation
        $quote_url = get_permalink($quote_id);
        if ($quote_url) {
            wp_redirect(add_query_arg('auto_download_pdf', '1', $quote_url));
            exit;
        } else {
            $this->sendError(__('Could not generate quote URL', 'easy-invoice'));
        }
    }

    /**
     * Search clients for the dropdown
     */
    public function searchClients() {
        $this->verifyNonce('easy_invoice_nonce');

        if (!current_user_can('manage_options')) {
            $this->sendError(__('You do not have permission to perform this action', 'easy-invoice'));
        }

        $query = isset($_POST['query']) ? sanitize_text_field($_POST['query']) : '';

        // Get client repository
        $client_repository = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository();

        // Search clients
        $clients = $client_repository->search($query);

        if (empty($clients)) {
            $this->sendSuccess(array());
        }

        // Format clients for dropdown
        $formatted_clients = array();
        foreach ($clients as $client) {
            // Get the WordPress user data directly
            $user = get_user_by('id', $client->getId());
            if (!$user) {
                continue;
            }

            // Use Client model properties first, fallback to WordPress user fields
            $business_name = $client->business_client_name ?: '';
            $first_name = $client->first_name ?: $user->first_name ?: '';
            $last_name = $client->last_name ?: $user->last_name ?: '';
            $email = $client->email ?: $user->user_email ?: '';



            // Create display name
            $client_name = $business_name ?: ($first_name . ' ' . $last_name);
            if (empty(trim($client_name))) {
                $client_name = $user->display_name ?: 'User ' . $client->getId();
            }

            // Include all clients, even those with empty emails
            $formatted_clients[] = array(
                'id' => $client->getId(),
                'name' => $client_name,
                'email' => $email,
                'company' => $business_name,
                'phone' => $client->phone ?: '',
                'address' => $client->address ?: '',
                'website' => $client->website ?: '',
                'display_name' => $client_name . ' (' . $email . ')'
            );
        }

        $this->sendSuccess($formatted_clients);
    }

    /**
     * Save additional CSS for invoice/quote
     */
    public function saveAdditionalCSS() {
        // Verify nonce
        if (!wp_verify_nonce($_POST['nonce'], 'save_additional_css_nonce')) {
            $this->sendError('Security check failed');
            return;
        }

        // Check user capabilities - require administrator
        if (!current_user_can('manage_options')) {
            $this->sendError('You do not have permission to perform this action');
            return;
        }

        // Validate and sanitize post ID
        $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
        if ($post_id <= 0) {
            $this->sendError('Invalid post ID');
            return;
        }

        // Verify post exists and user can edit it
        $post = get_post($post_id);
        if (!$post || !current_user_can('edit_post', $post_id)) {
            $this->sendError('You cannot edit this post');
            return;
        }

        // Verify post type is invoice or quote
        $valid_post_types = [
            \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE,
            \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE
        ];
        if (!in_array($post->post_type, $valid_post_types)) {
            $this->sendError('Invalid post type');
            return;
        }

        // Get and sanitize CSS content
        $css = isset($_POST['css']) ? $_POST['css'] : '';
        
        // Enhanced CSS sanitization
        $css = $this->sanitizeCSS($css);

        // Limit CSS length to prevent abuse
        if (strlen($css) > 50000) { // 50KB limit
            $this->sendError('CSS content too long');
            return;
        }

        // Save CSS to post meta
        $result = update_post_meta($post_id, '_easy_invoice_additional_css', $css);

        if ($result !== false) {
            $this->sendSuccess(array(
                'message' => 'CSS saved successfully',
                'css' => $css,
                'post_id' => $post_id
            ));
        } else {
            $this->sendError('Failed to save CSS');
        }
    }

    /**
     * Enhanced CSS sanitization - preserves valid CSS while removing threats
     */
    private function sanitizeCSS($css) {
        // Remove PHP tags first
        $css = preg_replace('/<\?php.*?\?>/is', '', $css);
        
        // Remove HTML tags (script, iframe, object, embed)
        $css = preg_replace('/<script[^>]*>.*?<\/script>/is', '', $css);
        $css = preg_replace('/<iframe[^>]*>.*?<\/iframe>/is', '', $css);
        $css = preg_replace('/<object[^>]*>.*?<\/object>/is', '', $css);
        $css = preg_replace('/<embed[^>]*>/is', '', $css);
        
        // Remove dangerous CSS constructs
        $css = preg_replace('/expression\s*\(/i', '', $css); // CSS expressions
        $css = preg_replace('/javascript\s*:/i', '', $css); // JavaScript protocol
        $css = preg_replace('/@import\s+url\s*\(/i', '', $css); // @import url()
        $css = preg_replace('/@import\s+["\'][^"\']+["\']/', '', $css); // @import with quotes
        $css = preg_replace('/behavior\s*:\s*url\s*\(/i', '', $css); // IE behavior
        $css = preg_replace('/binding\s*:/i', '', $css); // XBL binding
        
        // Remove dangerous CSS functions (but keep safe ones)
        $dangerous_functions = ['eval', 'exec', 'system', 'passthru', 'shell_exec', 'phpinfo', 'file_get_contents', 'file_put_contents', 'fopen', 'fwrite', 'curl_exec'];
        foreach ($dangerous_functions as $func) {
            $css = preg_replace('/\b' . preg_quote($func, '/') . '\s*\(/i', '', $css);
        }
        
        // Remove data URLs that could contain malicious content
        $css = preg_replace('/data\s*:\s*["\'][^"\']*["\']/i', '', $css);
        
        // Remove vbscript: protocol
        $css = preg_replace('/vbscript\s*:/i', '', $css);
        
        // Remove any remaining HTML-like constructs
        $css = htmlspecialchars_decode($css, ENT_QUOTES);
        
        // Basic cleanup - remove excessive whitespace but preserve CSS structure
        $css = preg_replace('/\s+/', ' ', $css);
        $css = preg_replace('/;\s*}/', '}', $css);
        $css = preg_replace('/\s*{\s*/', ' {', $css);
        $css = preg_replace('/;\s*;/', ';', $css);
        
        return trim($css);
    }
}

```
