config_cache = null; // Also clear any WordPress object cache wp_cache_delete('easy_invoice_settings_config', 'easy_invoice'); // Force clear any other caches if (function_exists('wp_cache_flush')) { wp_cache_flush(); } } /** * Initialize the controller * * @return void */ public function init() { // Add AJAX handlers add_action('wp_ajax_easy_invoice_save_settings', [$this, 'saveSettings']); add_action('wp_ajax_easy_invoice_test_email', [$this, 'testEmail']); add_action('wp_ajax_easy_invoice_test_template_email', [$this, 'testTemplateEmail']); add_action('wp_ajax_easy_invoice_test_payment_reminder_email', [$this, 'testPaymentReminderEmail']); add_action('wp_ajax_regenerate_invoice_numbers', [$this, 'ajaxRegenerateInvoiceNumbers']); add_action('wp_ajax_regenerate_quote_numbers', [$this, 'ajaxRegenerateQuoteNumbers']); // Clear cache on admin init to ensure new fields are recognized add_action('admin_init', [$this, 'clearCache']); // Enqueue admin scripts add_action('admin_enqueue_scripts', [$this, 'enqueueAdminScripts']); // Initialize settings $this->initializeSettings(); } /** * Enqueue admin scripts and styles for the settings page. * * @param string $hook_suffix The current admin page. * @return void */ public function enqueueAdminScripts($hook_suffix) { // Check if we are on the Easy Invoice settings page. if (empty($hook_suffix) || strpos($hook_suffix, PagesSlugs::SETTINGS) === false) { return; } wp_enqueue_script('jquery-ui-sortable'); wp_enqueue_media(); // For the logo uploader // Enqueue Select2 for enhancing multiselect fields wp_enqueue_style( 'select2', EASY_INVOICE_URL . 'assets/lib/select2/select2.min.css', [], '4.0.13' ); wp_enqueue_script( 'select2', EASY_INVOICE_URL . 'assets/lib/select2/select2.min.js', ['jquery'], '4.0.13', true ); // Enqueue confirmation modal for premium features wp_enqueue_script( 'easy-invoice-confirmation-modal', EASY_INVOICE_URL . 'assets/js/confirmation-modal.js', ['jquery'], EASY_INVOICE_VERSION, true ); // Enqueue toast notification system wp_enqueue_script( 'easy-invoice-toast', EASY_INVOICE_URL . 'assets/js/easy-invoice-toast.js', ['jquery'], EASY_INVOICE_VERSION, true ); $script_handle = 'easy-invoice-settings'; wp_enqueue_script( $script_handle, EASY_INVOICE_URL . 'assets/js/settings.js', ['jquery', 'jquery-ui-sortable', 'wp-util', 'select2', 'easy-invoice-confirmation-modal', 'media-views'], EASY_INVOICE_VERSION, true ); // Enqueue the new settings.css only on the settings page wp_enqueue_style( 'easy-invoice-settings', EASY_INVOICE_URL . 'assets/css/settings.css', [], EASY_INVOICE_VERSION ); $localize_data = apply_filters('easy_invoice_settings_js_data', [ 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('easy_invoice_settings'), 'saveSuccess' => __('Settings saved successfully', 'easy-invoice'), 'saveError' => __('Error saving settings', 'easy-invoice'), ]); wp_localize_script($script_handle, 'easyInvoiceSettings', $localize_data); do_action('easy_invoice_after_admin_scripts', $hook_suffix); } /** * Check if Easy Invoice Pro is active * * @return bool */ private function isProActive(): bool { return easy_invoice_has_pro(); } /** * Get settings fields configuration * * @return array */ public function get_settings_fields_config() { // Use cached config if available if (is_array($this->config_cache)) { return $this->config_cache; } $config = [ 'company' => [ 'title' => __('Company Information', 'easy-invoice'), 'description' => __('Set up your company details', 'easy-invoice'), 'icon' => 'fas fa-building', 'fields' => [ 'easy_invoice_company_name' => ['label' => __('Company Name', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6' ], 'easy_invoice_company_email' => ['label' => __('Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-6' ], 'easy_invoice_company_phone' => ['label' => __('Phone Number', 'easy-invoice'), 'type' => 'tel', 'default' => '', 'col_span' => 'sm:col-span-6' ], 'easy_invoice_company_website' => ['label' => __('Website', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6' ], // Both are required by EN 16931 and are what makes an intra-EU // reverse charge determinable. See Services\TaxTreatment. 'easy_invoice_company_vat_number' => ['label' => __('VAT number', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3', 'description' => __('Your VAT identifier, including the country prefix — for example DE123456789.', 'easy-invoice') ], 'easy_invoice_company_country' => ['label' => __('Country', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3', 'description' => __('Two-letter country code, for example DE, FR or GB.', 'easy-invoice') ], 'easy_invoice_company_address' => ['label' => __('Address', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6' ], 'easy_invoice_tax_number' => ['label' => __('Tax ID / VAT Number', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6' ], 'easy_invoice_company_logo' => ['label' => __('Company Logo', 'easy-invoice'), 'type' => 'image', 'default' => '', 'col_span' => 'sm:col-span-6' ], ] ], 'invoice' => [ 'title' => __('Invoice Settings', 'easy-invoice'), 'description' => __('Configure invoice-specific settings and preferences', 'easy-invoice'), 'icon' => 'fas fa-file-invoice', 'fields' => [ 'easy_invoice_invoice_prefix' => [ 'label' => __('Invoice Prefix', 'easy-invoice'), 'type' => 'text', 'default' => 'EIIN_', 'description' => __('Prefix for invoice numbers (e.g., EIIN_, INV-)', 'easy-invoice'), 'col_span' => 'sm:col-span-2' ], 'easy_invoice_next_invoice_number' => [ 'label' => __('Next Invoice Number', 'easy-invoice'), 'type' => 'number', 'default' => '1', 'description' => __('Set the next invoice number to be generated. This will be the number of the next invoice created.', 'easy-invoice'), 'col_span' => 'sm:col-span-2', ], 'easy_invoice_regenerate_invoice_numbers' => [ 'label' => __('Regenerate Invoice Numbers', 'easy-invoice'), 'type' => 'button', 'button_text' => __('Regenerate All Invoice Numbers', 'easy-invoice'), 'button_class' => 'button button-secondary', 'description' => __('Click to regenerate all invoice numbers starting from the Last Invoice Number. This will update all existing invoices with new sequential numbers.', 'easy-invoice'), 'col_span' => 'sm:col-span-2', 'ajax_action' => 'regenerate_invoice_numbers' ], 'easy_invoice_invoice_show_adjust_field' => [ 'label' => __('Show/Hide Adjust Field', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'description' => __('Enable/Disable Adjust field. Tick this to show adjust field', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_invoice_terms_conditions' => [ 'label' => __('Terms & Conditions', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('Payment is due within 30 days from date of invoice', 'easy-invoice'), 'description' => __('Terms and conditions that will be displayed on your invoice', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_invoice_footer_text' => [ 'label' => __('Footer Text', 'easy-invoice'), 'type' => 'wp_editor', 'default' => '', 'description' => __('You can modify your invoice footer text from here. HTML tags supports: a, br, em, strong, hr, p, h1 to h4', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], ], ], 'quote' => [ 'title' => __('Quote Settings', 'easy-invoice'), 'description' => __('Configure quote-specific settings and preferences', 'easy-invoice'), 'icon' => 'fas fa-file-contract', 'fields' => [ 'easy_invoice_quote_prefix' => [ 'label' => __('Quote Prefix', 'easy-invoice'), 'type' => 'text', 'default' => 'EIQN_', 'description' => __('Prefix for quote numbers (e.g., EIQN_, QT-)', 'easy-invoice'), 'col_span' => 'sm:col-span-2' ], 'easy_invoice_next_quote_number' => [ 'label' => __('Next Quote Number', 'easy-invoice'), 'type' => 'number', 'default' => '1', 'description' => __('Set the next quote number to be generated. This will be the number of the next quote created.', 'easy-invoice'), 'col_span' => 'sm:col-span-2', ], 'easy_invoice_regenerate_quote_numbers' => [ 'label' => __('Regenerate Quote Numbers', 'easy-invoice'), 'type' => 'button', 'button_text' => __('Regenerate All Quote Numbers', 'easy-invoice'), 'button_class' => 'button button-secondary', 'description' => __('Click to regenerate all quote numbers starting from the Next Quote Number. This will update all existing quotes with new sequential numbers.', 'easy-invoice'), 'col_span' => 'sm:col-span-2', 'ajax_action' => 'regenerate_quote_numbers' ], 'easy_invoice_quote_show_adjust_field' => [ 'label' => __('Show/Hide Adjust Field', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'description' => __('Enable/Disable Adjust field. Tick this to show adjust field', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_quote_terms_conditions' => [ 'label' => __('Terms & Conditions', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('This quote has a fixed price. Upon acceptance, we kindly ask for a 25% deposit prior to initiating the work.', 'easy-invoice'), 'description' => __('Terms and conditions that will be displayed on your quote!', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_quote_footer_text' => [ 'label' => __('Footer Text', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('Thank you for your business.', 'easy-invoice'), 'description' => __('You can modify your quote footer text from here. HTML tags supports: a, br, em, strong, hr, p, h1 to h4', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_quote_accept_button' => [ 'label' => __('Accept quote button', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'description' => __('Show/hide accept quote button on quotes.', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_quote_accept_action' => [ 'label' => __('Accept quote button action', 'easy-invoice'), 'type' => 'select', 'default' => 'convert', 'options' => [ 'convert' => __('Convert quote to invoice - Draft', 'easy-invoice'), 'convert_available' => __('Convert quote to invoice - Available', 'easy-invoice'), 'convert_send' => __('Convert quote to invoice and send to client - Available', 'easy-invoice'), 'duplicate' => __('Create new invoice, keep quote as-is - Draft', 'easy-invoice'), 'duplicate_send' => __('Create new invoice and send to client, keep quote as-is - Available', 'easy-invoice'), 'do_nothing' => __('Do nothing', 'easy-invoice'), ], 'description' => __('Upon the client clicking the "accept quote" button, the subsequent action will be activated.', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_quote_accept_text' => [ 'label' => __('Accept Quote Text', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('Important: When you accept this Quote, an Invoice will be created automatically. This will form a legally binding contract.', 'easy-invoice'), 'description' => __('This information tells your client what happens once they accept the Quote', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_quote_accepted_message' => [ 'label' => __('Accepted Quote Message', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('You\'ve confirmed the Quote.
We\'ll get in touch with you shortly.', 'easy-invoice'), 'description' => __('If the client accepts the Quote, display this message.', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_quote_decline_reason_required' => [ 'label' => __('Decline Reason Required', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'description' => __('Make the \'Reason for declining\' field mandatory when rejecting.', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_quote_declined_message' => [ 'label' => __('Declined Quote Message', 'easy-invoice'), 'type' => 'wp_editor', 'default' => '', 'description' => __('Message to display if client declines the Quote', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], ], ], 'currency' => [ 'title' => __('Currency Settings', 'easy-invoice'), 'description' => __('Configure currency display preferences', 'easy-invoice'), 'icon' => 'fas fa-dollar-sign', 'fields' => [ 'easy_invoice_currency_code' => [ 'label' => __('Currency & Symbol', 'easy-invoice'), 'type' => 'select', 'default' => 'USD', 'options' => CurrencyHelper::getCurrencyOptions(), 'col_span' => 'sm:col-span-6', 'description' => __('Select your preferred currency. The symbol will be automatically set.', 'easy-invoice') ], 'easy_invoice_currency_position' => [ 'label' => __('Symbol Position', 'easy-invoice'), 'type' => 'select', 'default' => 'left', 'options' => [ 'left' => __('Left', 'easy-invoice'), 'right' => __('Right', 'easy-invoice'), 'left_space' => __('Left with space', 'easy-invoice'), 'right_space' => __('Right with space', 'easy-invoice'), ], 'col_span' => 'sm:col-span-3' ], 'easy_invoice_currency_symbol_type' => [ 'label' => __('Currency Symbol Type', 'easy-invoice'), 'type' => 'select', 'default' => 'symbol', 'options' => [ 'code' => __('Currency Code', 'easy-invoice'), 'symbol' => __('Currency Symbol', 'easy-invoice'), ], 'description' => __('Choose whether to display currency code (USD) or currency symbol ($)', 'easy-invoice'), 'col_span' => 'sm:col-span-3' ], 'easy_invoice_thousands_separator' => ['label' => __('Thousands Separator', 'easy-invoice'), 'type' => 'text', 'default' => ',', 'col_span' => 'sm:col-span-3' ], 'easy_invoice_decimal_separator' => ['label' => __('Decimal Separator', 'easy-invoice'), 'type' => 'text', 'default' => '.', 'col_span' => 'sm:col-span-3' ], 'easy_invoice_decimal_precision' => ['label' => __('Decimal Places', 'easy-invoice'), 'type' => 'number', 'default' => '2', 'min' => 0, 'max' => 4, 'col_span' => 'sm:col-span-3' ], ] ], 'tax' => [ 'title' => __('Tax Settings', 'easy-invoice'), 'description' => __('Configure tax rates and preferences', 'easy-invoice'), 'icon' => 'fas fa-percent', 'fields' => [ 'easy_invoice_tax_enabled' => ['label' => __('Enable Tax', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6' ], 'easy_invoice_tax_entry_method' => [ 'label' => __('How do you enter tax?', 'easy-invoice'), 'type' => 'select', 'default' => 'exclusive', 'options' => [ 'inclusive' => __('I will enter price inclusive of tax', 'easy-invoice'), 'exclusive' => __('I will enter price exclusive of tax', 'easy-invoice'), ], 'description' => __('Choose how you want to enter prices - with or without tax included', 'easy-invoice'), 'col_span' => 'sm:col-span-6' ], 'easy_invoice_tax_rate' => ['label' => __('Default Tax Rate (%)', 'easy-invoice'), 'type' => 'number', 'default' => '0', 'step' => '0.01', 'min' => '0', 'max' => '100', 'col_span' => 'sm:col-span-3' ], 'easy_invoice_tax_name' => ['label' => __('Tax Name', 'easy-invoice'), 'type' => 'text', 'default' => __('Tax', 'easy-invoice'), 'col_span' => 'sm:col-span-3' ], ] ], 'payment' => [ 'title' => __('Payment Methods', 'easy-invoice'), 'description' => __('Configure and order available payment methods.', 'easy-invoice'), 'icon' => 'fas fa-credit-card', 'is_special_section' => true, 'gateways' => $this->getGatewaySettingsConfigs(), ], 'email' => [ 'title' => __('Email Settings', 'easy-invoice'), 'description' => __('Configure email sending options and templates', 'easy-invoice'), 'icon' => 'fas fa-envelope', 'subsections' => [ 'general' => [ 'title' => __('General Email Settings', 'easy-invoice'), 'description' => __('Configure general email sending options', 'easy-invoice'), 'fields' => [ 'easy_invoice_email_from_name' => ['label' => __('From Name', 'easy-invoice'), 'type' => 'text', 'default' => get_bloginfo('name') ?: 'Easy Invoice', 'col_span' => 'sm:col-span-3'], 'easy_invoice_email_from_address' => ['label' => __('From Email Address', 'easy-invoice'), 'type' => 'email', 'default' => get_bloginfo('admin_email') ?: get_option('admin_email', ''), 'col_span' => 'sm:col-span-3'], 'easy_invoice_email_reply_to' => ['label' => __('Reply-To Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-3'], 'easy_invoice_email_reply_to_name' => ['label' => __('Reply-To Name', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3'], 'easy_invoice_enable_email_styling' => ['label' => __('Enable HTML Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'], 'easy_invoice_bcc_admin' => ['label' => __('BCC Admin on All Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6'], // Off by default: attaching a document changes what every // customer receives and makes messages much larger, which // some SMTP relays reject. See EmailManager::shouldAttachInvoicePdf(). 'easy_invoice_attach_pdf_to_email' => ['label' => __('Attach a PDF copy to invoice and quote emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6', 'description' => __('Generates the document on the server and attaches it. The PDF contains real, selectable text rather than an image.', 'easy-invoice')], 'easy_invoice_pdf_paper_size' => ['label' => __('PDF paper size', 'easy-invoice'), 'type' => 'select', 'default' => 'a4', 'options' => ['a4' => 'A4', 'letter' => 'Letter', 'legal' => 'Legal'], 'col_span' => 'sm:col-span-3'], 'easy_invoice_pdf_download_method' => ['label' => __('"Download as PDF" button', 'easy-invoice'), 'type' => 'select', 'default' => 'browser', 'options' => ['browser' => __('Exactly as shown on screen (rendered in the browser)', 'easy-invoice'), 'server' => __('Generated on the server (real text, smaller file)', 'easy-invoice')], 'col_span' => 'sm:col-span-3', 'description' => __('The browser capture reproduces the page pixel for pixel — every design, custom template and watermark. The server file is selectable text and a fraction of the size. Emails, the API and e-invoicing always use the server.', 'easy-invoice')], 'easy_invoice_pdf_layout' => ['label' => __('Server-generated PDF layout', 'easy-invoice'), 'type' => 'select', 'default' => 'design', 'options' => ['design' => __('Match the selected invoice / quote design', 'easy-invoice'), 'plain' => __('Plain print layout', 'easy-invoice')], 'col_span' => 'sm:col-span-3', 'description' => __('Applies to PDFs the server produces: email attachments, the API, e-invoicing, and the download button when set to "server". The design is the template chosen on the document; the plain layout is a neutral table-based page.', 'easy-invoice')], 'easy_invoice_admin_email' => ['label' => __('Admin Email for BCC', 'easy-invoice'), 'type' => 'email', 'default' => get_option('admin_email', ''), 'col_span' => 'sm:col-span-6'], 'easy_invoice_email_logo' => ['label' => __('Email Logo URL', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6'], 'easy_invoice_email_footer_text' => ['label' => __('Email Footer Text', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6'], 'easy_invoice_test_email' => [ 'label' => __('Test Email', 'easy-invoice'), 'type' => 'test_email', 'default' => '', 'col_span' => 'sm:col-span-6', 'description' => __('Send a test email to verify your email configuration is working correctly.', 'easy-invoice') ], ] ], 'invoice_available' => [ 'title' => __('Invoice Available Email', 'easy-invoice'), 'description' => __('Configure email sent when an invoice is available for the client', 'easy-invoice'), 'fields' => [ 'easy_invoice_invoice_email_enabled' => ['label' => __('Enable Invoice Available Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'], 'easy_invoice_invoice_email_subject' => ['label' => __('Subject', 'easy-invoice'), 'type' => 'text', 'default' => __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'), 'col_span' => 'sm:col-span-6'], 'easy_invoice_invoice_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'), 'col_span' => 'sm:col-span-6'], 'easy_invoice_invoice_test_template' => [ 'label' => __('Test Invoice Email', 'easy-invoice'), 'type' => 'test_template_email', 'default' => '', 'col_span' => 'sm:col-span-6', 'description' => __('Send a test invoice email to verify the template and configuration.', 'easy-invoice'), 'template_type' => 'invoice_available' ], ] ], 'quote_available' => [ 'title' => __('Quote Available Email', 'easy-invoice'), 'description' => __('Configure email sent when a quote is available for the client', 'easy-invoice'), 'fields' => [ 'easy_invoice_quote_email_enabled' => ['label' => __('Enable Quote Available Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'], 'easy_invoice_quote_email_subject' => ['label' => __('Subject', 'easy-invoice'), 'type' => 'text', 'default' => __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'), 'col_span' => 'sm:col-span-6'], 'easy_invoice_quote_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => \EasyInvoice\Services\EmailManager::defaultTemplate('quote'), 'col_span' => 'sm:col-span-6'], 'easy_invoice_quote_test_template' => [ 'label' => __('Test Quote Email', 'easy-invoice'), 'type' => 'test_template_email', 'default' => '', 'col_span' => 'sm:col-span-6', 'description' => __('Send a test quote email to verify the template and configuration.', 'easy-invoice'), 'template_type' => 'quote_available' ], ] ], 'payment_received' => [ 'title' => __('Payment Received Email', 'easy-invoice'), 'description' => __('Configure email sent when a payment is received', 'easy-invoice'), 'fields' => [ 'easy_invoice_payment_email_enabled' => ['label' => __('Enable Payment Received Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'], 'easy_invoice_payment_email_subject' => ['label' => __('Subject', 'easy-invoice'), 'type' => 'text', 'default' => __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'), 'col_span' => 'sm:col-span-6'], 'easy_invoice_payment_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => \EasyInvoice\Services\EmailManager::defaultTemplate('payment'), 'col_span' => 'sm:col-span-6'], 'easy_invoice_payment_test_template' => [ 'label' => __('Test Payment Email', 'easy-invoice'), 'type' => 'test_template_email', 'default' => '', 'col_span' => 'sm:col-span-6', 'description' => __('Send a test payment email to verify the template and configuration.', 'easy-invoice'), 'template_type' => 'payment_received' ], ] ], ] ], 'text_settings' => [ 'title' => __('Text Settings', 'easy-invoice'), 'description' => __('Customize text labels for invoices and quotes. Perfect for multi-language support.', 'easy-invoice'), 'icon' => ' ', 'fields' => [ // Invoice Text Settings 'easy_invoice_text_invoice' => ['label' => __('Invoice', 'easy-invoice'), 'type' => 'text', 'default' => __('Invoice', 'easy-invoice'), 'col_span' => '', 'description' => __('Label for single invoice', 'easy-invoice')], 'easy_invoice_text_invoices' => ['label' => __('Invoices', 'easy-invoice'), 'type' => 'text', 'default' => __('Invoices', 'easy-invoice'), 'col_span' => '', 'description' => __('Label for multiple invoices', 'easy-invoice')], 'easy_invoice_text_to' => ['label' => __('To', 'easy-invoice'), 'type' => 'text', 'default' => __('To', 'easy-invoice'), 'col_span' => '', 'description' => __('To label on invoice/quote', 'easy-invoice')], 'easy_invoice_text_invoice_number' => ['label' => __('Invoice Number', 'easy-invoice'), 'type' => 'text', 'default' => __('Invoice Number', 'easy-invoice'), 'col_span' => '', 'description' => __('Invoice number label', 'easy-invoice')], 'easy_invoice_text_invoice_date' => ['label' => __('Invoice Date', 'easy-invoice'), 'type' => 'text', 'default' => __('Invoice Date', 'easy-invoice'), 'col_span' => '', 'description' => __('Invoice date label', 'easy-invoice')], 'easy_invoice_text_due_date' => ['label' => __('Due Date', 'easy-invoice'), 'type' => 'text', 'default' => __('Due Date', 'easy-invoice'), 'col_span' => '', 'description' => __('Due date label', 'easy-invoice')], 'easy_invoice_text_total_due' => ['label' => __('Total Due', 'easy-invoice'), 'type' => 'text', 'default' => __('Total Due', 'easy-invoice'), 'col_span' => '', 'description' => __('Total due label', 'easy-invoice')], 'easy_invoice_text_qty' => ['label' => __('Qty', 'easy-invoice'), 'type' => 'text', 'default' => __('Qty', 'easy-invoice'), 'col_span' => '', 'description' => __('Quantity label', 'easy-invoice')], 'easy_invoice_text_service' => ['label' => __('Service', 'easy-invoice'), 'type' => 'text', 'default' => __('Service', 'easy-invoice'), 'col_span' => '', 'description' => __('Service label', 'easy-invoice')], 'easy_invoice_text_rate_price' => ['label' => __('Rate', 'easy-invoice'), 'type' => 'text', 'default' => __('Rate', 'easy-invoice'), 'col_span' => '', 'description' => __('Rate or price label', 'easy-invoice')], 'easy_invoice_text_adjust' => ['label' => __('Adjust', 'easy-invoice'), 'type' => 'text', 'default' => __('Adjust', 'easy-invoice'), 'col_span' => '', 'description' => __('Adjust label', 'easy-invoice')], 'easy_invoice_text_sub_total' => ['label' => __('Sub Total', 'easy-invoice'), 'type' => 'text', 'default' => __('Sub Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Sub total label', 'easy-invoice')], 'easy_invoice_text_total' => ['label' => __('Total', 'easy-invoice'), 'type' => 'text', 'default' => __('Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Total label', 'easy-invoice')], 'easy_invoice_text_line_total' => ['label' => __('Line total', 'easy-invoice'), 'type' => 'text', 'default' => __('Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Heading of the line-item amount column', 'easy-invoice')], 'easy_invoice_text_tax' => ['label' => __('Tax', 'easy-invoice'), 'type' => 'text', 'default' => __('Tax', 'easy-invoice'), 'col_span' => '', 'description' => __('Tax label', 'easy-invoice')], 'easy_invoice_text_discount' => ['label' => __('Discount', 'easy-invoice'), 'type' => 'text', 'default' => __('Discount', 'easy-invoice'), 'col_span' => '', 'description' => __('Discount label', 'easy-invoice')], 'easy_invoice_text_print' => ['label' => __('Print', 'easy-invoice'), 'type' => 'text', 'default' => __('Print', 'easy-invoice'), 'col_span' => '', 'description' => __('Print button text', 'easy-invoice')], 'easy_invoice_text_download_pdf' => ['label' => __('Download as PDF', 'easy-invoice'), 'type' => 'text', 'default' => __('Download as PDF', 'easy-invoice'), 'col_span' => '', 'description' => __('Download PDF button text', 'easy-invoice')], 'easy_invoice_text_send_email' => ['label' => __('Send Email', 'easy-invoice'), 'type' => 'text', 'default' => __('Send Email', 'easy-invoice'), 'col_span' => '', 'description' => __('Send email button text', 'easy-invoice')], 'easy_invoice_text_pay_now' => ['label' => __('Pay Now', 'easy-invoice'), 'type' => 'text', 'default' => __('Pay Now', 'easy-invoice'), 'col_span' => '', 'description' => __('Pay now button text', 'easy-invoice')], // Quote Text Settings 'easy_invoice_text_quote' => ['label' => __('Quote', 'easy-invoice'), 'type' => 'text', 'default' => __('Quote', 'easy-invoice'), 'col_span' => '', 'description' => __('Label for single quote', 'easy-invoice')], 'easy_invoice_text_quote_number' => ['label' => __('Quote Number', 'easy-invoice'), 'type' => 'text', 'default' => __('Quote Number', 'easy-invoice'), 'col_span' => '', 'description' => __('Quote number label', 'easy-invoice')], 'easy_invoice_text_accept_quote' => ['label' => __('Accept Quote', 'easy-invoice'), 'type' => 'text', 'default' => __('Accept Quote', 'easy-invoice'), 'col_span' => '', 'description' => __('Accept quote button text', 'easy-invoice')], 'easy_invoice_text_decline_quote' => ['label' => __('Decline Quote', 'easy-invoice'), 'type' => 'text', 'default' => __('Decline Quote', 'easy-invoice'), 'col_span' => '', 'description' => __('Decline quote button text', 'easy-invoice')], 'easy_invoice_text_decline_reason' => ['label' => __('Reason for declining', 'easy-invoice'), 'type' => 'text', 'default' => __('Reason for declining', 'easy-invoice'), 'col_span' => '', 'description' => __('Decline reason label', 'easy-invoice')], 'easy_invoice_text_valid_until' => ['label' => __('Valid Until Date', 'easy-invoice'), 'type' => 'text', 'default' => __('Valid Until Date', 'easy-invoice'), 'col_span' => '', 'description' => __('Valid until date label', 'easy-invoice')], 'easy_invoice_text_quote_date' => ['label' => __('Quote Date', 'easy-invoice'), 'type' => 'text', 'default' => __('Quote Date', 'easy-invoice'), 'col_span' => '', 'description' => __('Quote date label', 'easy-invoice')], ] ], 'advanced' => [ 'title' => __('Advanced Settings', 'easy-invoice'), 'description' => __('Configure system preferences', 'easy-invoice'), 'icon' => 'fas fa-cog', 'fields' => [ 'easy_invoice_date_format' => [ 'label' => __('Date Format', 'easy-invoice'), 'type' => 'select', 'default' => 'us', 'options' => [ 'us' => __('MM/DD/YYYY (US) - 01/15/2024', 'easy-invoice'), 'uk' => __('DD/MM/YYYY (UK) - 15/01/2024', 'easy-invoice'), 'iso' => __('YYYY-MM-DD (ISO) - 2024-01-15', 'easy-invoice'), ], 'col_span' => 'sm:col-span-3' ], 'easy_invoice_invoice_numbering' => ['label' => __('Auto-increment invoice numbers', 'easy-invoice'), 'type' => 'checkbox', 'description' => __('Automatically increment invoice numbers for new invoices', 'easy-invoice'), 'default' => 'yes', 'col_span' => 'sm:col-span-6' ], 'easy_invoice_payment_reminder_days' => ['label' => __('Payment Reminder Days', 'easy-invoice'), 'type' => 'number', 'default' => 3, 'col_span' => 'sm:col-span-3'], 'easy_invoice_remove_data_on_uninstall' => ['label' => __('Remove all data when the plugin is deleted', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6', 'description' => __('Off, deleting the plugin from the Plugins screen keeps every invoice, quote, payment, client record and setting, so reinstalling picks up where you left off. On, all of it is removed — invoices are tax records, so leave this off unless you are certain.', 'easy-invoice')], ] ], ]; // Cache and filter the config $this->config_cache = apply_filters('easy_invoice_settings_fields_config', $config); return $this->config_cache; } /** * Get all gateway settings configurations * * @return array Gateway settings configs */ private function getGatewaySettingsConfigs(): array { $gateway_configs = []; try { // Get gateway manager from the main plugin instance $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager(); $gateways = $gateway_manager->getGateways(); // Collect settings from each gateway foreach ($gateways as $gateway_id => $gateway) { try { $gateway_configs[$gateway_id] = $gateway->getSettingsConfig(); } catch (\Exception $e) { // Log the error but continue with other gateways $gateway_configs[$gateway_id] = []; } } } catch (\Exception $e) { } return $gateway_configs; } /** * Prepare settings for display with proper filtering * * @param array $settings The settings array * @return array The filtered settings */ public function prepareSettingsForDisplay($settings) { return apply_filters('easy_invoice_settings_for_display', $settings); } /** * Display method implementation * * @param array $args Display arguments * @return void */ public function display(array $args = []) { $page = isset($args['page']) ? $args['page'] : ''; $subsection = isset($args['subsection']) ? $args['subsection'] : ''; // Clear cache to ensure new fields are recognized $this->clearCache(); switch ($page) { case PagesSlugs::SETTINGS: if (!empty($subsection)) { // Handle email settings subsections $this->displayEmailSettingsPage($subsection); } else { $this->displaySettingsPage(); } break; default: $this->displaySettingsPage(); break; } } /** * Display email settings page for specific subsection * * @param string $subsection The subsection to display * @return void */ protected function displayEmailSettingsPage($subsection) { // Get settings configuration $settings_config = $this->get_settings_fields_config(); $settings = $this->getSettings(); // Filter to show only email settings $email_config = $settings_config['email'] ?? []; // Map subsection to the appropriate subsection in email config $subsection_mapping = [ PagesSlugs::EMAIL_SETTINGS_GENERAL => 'general', PagesSlugs::EMAIL_SETTINGS_INVOICE => 'invoice_available', PagesSlugs::EMAIL_SETTINGS_QUOTE => 'quote_available', PagesSlugs::EMAIL_SETTINGS_PAYMENT => 'payment_received', PagesSlugs::EMAIL_SETTINGS_PAYMENT_REMINDER => 'payment_reminder', ]; $target_subsection = $subsection_mapping[$subsection] ?? 'general'; // Create a modified config with only the target subsection $filtered_config = [ 'email' => [ 'title' => __('Email Settings', 'easy-invoice'), 'description' => __('Configure email sending options and templates', 'easy-invoice'), 'icon' => 'fas fa-envelope', 'subsections' => [ $target_subsection => $email_config['subsections'][$target_subsection] ?? [] ] ] ]; // Include the settings page template with filtered config include EASY_INVOICE_PLUGIN_DIR . 'templates/settings-page.php'; } /** * Display settings page * * @return void */ protected function displaySettingsPage() { $settings = $this->getSettings(); // Allow developers to modify settings before display $settings = apply_filters('easy_invoice_before_display_settings', $settings); // Ensure payment methods are set (this is critical for gateway checkboxes) if (!isset($settings['easy_invoice_payment_methods']) || !is_array($settings['easy_invoice_payment_methods'])) { $settings['easy_invoice_payment_methods'] = get_option('easy_invoice_payment_methods', []); } // Ensure gateway order is set if (!isset($settings['easy_invoice_payment_gateway_order']) || !is_array($settings['easy_invoice_payment_gateway_order'])) { $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []); } $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager(); $all_gateways_unsorted = $gateway_manager->getGateways(); $gateway_order_option = $settings['easy_invoice_payment_gateway_order']; $payment_methods_enabled = $settings['easy_invoice_payment_methods']; $all_gateways_sorted = []; if (!empty($gateway_order_option)) { foreach ($gateway_order_option as $gateway_id) { if (isset($all_gateways_unsorted[$gateway_id])) { $all_gateways_sorted[$gateway_id] = $all_gateways_unsorted[$gateway_id]; unset($all_gateways_unsorted[$gateway_id]); } } } $all_gateways_sorted = array_merge($all_gateways_sorted, $all_gateways_unsorted); $settings_config = $this->get_settings_fields_config(); // Prepare template variables $template_args = apply_filters('easy_invoice_settings_template_args', [ 'settings' => $settings, 'settings_config' => $settings_config, 'all_gateways_sorted' => $all_gateways_sorted, 'payment_methods_enabled' => $payment_methods_enabled ]); // Display the template with template args $this->displayTemplate( apply_filters('easy_invoice_settings_template_path', EASY_INVOICE_PLUGIN_DIR . 'templates/settings-page.php'), $template_args ); // Action for plugins to add their own content after the settings page do_action('easy_invoice_after_settings_page'); } /** * Save payment settings * * @param array $posted_settings Posted settings * @param array $response Response array * @return void */ private function savePaymentSettings($posted_settings, &$response) { // Handle payment methods if (isset($posted_settings['easy_invoice_payment_methods']) && is_array($posted_settings['easy_invoice_payment_methods'])) { $sanitized_methods = array_map('sanitize_key', $posted_settings['easy_invoice_payment_methods']); update_option('easy_invoice_payment_methods', $sanitized_methods); $response['saved_options']['easy_invoice_payment_methods'] = $sanitized_methods; } else { update_option('easy_invoice_payment_methods', []); $response['saved_options']['easy_invoice_payment_methods'] = []; } // Handle gateway order if (isset($posted_settings['easy_invoice_gateway_order']) && is_array($posted_settings['easy_invoice_gateway_order'])) { $gateway_order = array_map('sanitize_key', $posted_settings['easy_invoice_gateway_order']); update_option('easy_invoice_payment_gateway_order', $gateway_order); $response['saved_options']['easy_invoice_gateway_order'] = $gateway_order; } else { update_option('easy_invoice_payment_gateway_order', []); $response['saved_options']['easy_invoice_gateway_order'] = []; } // Handle gateway display names $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager(); $all_gateways = $gateway_manager->getGateways(); foreach ($all_gateways as $gateway_id => $gateway) { $display_name_key = 'easy_invoice_gateway_display_name_' . $gateway_id; if (isset($posted_settings[$display_name_key])) { $display_name = wp_strip_all_tags(wp_unslash($posted_settings[$display_name_key])); update_option($display_name_key, $display_name); $response['saved_options'][$display_name_key] = $display_name; } } // Handle gateway-specific settings try { $gateway_configs = $this->getGatewaySettingsConfigs(); foreach ($gateway_configs as $gateway_id => $gateway_config) { if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) { foreach ($gateway_config['fields'] as $option_key => $field_config) { if (isset($posted_settings[$option_key])) { $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response); } elseif ($field_config['type'] === 'checkbox') { // Checkboxes not in POST are considered unchecked update_option($option_key, 'no'); $response['saved_options'][$option_key] = 'no'; } } } } } catch (\Exception $e) { throw $e; // Re-throw to be caught by the main try-catch } } /** * Save settings * * @return void */ public function saveSettings() { // Verify nonce. $_POST['easy_invoice_settings_nonce'] was read unguarded, so a // request without it raised "Undefined array key" before the check could run. $nonce = isset($_POST['easy_invoice_settings_nonce']) ? sanitize_text_field(wp_unslash($_POST['easy_invoice_settings_nonce'])) : ''; if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) { wp_die(esc_html__('Security check failed', 'easy-invoice')); } // Check permissions if (!current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to perform this action', 'easy-invoice')); } $posted_settings = $_POST['settings'] ?? []; $settings_config = $this->get_settings_fields_config(); $response = [ 'success' => true, 'message' => __('Settings saved successfully', 'easy-invoice'), 'saved_options' => [] ]; try { // Process each section foreach ($settings_config as $section_id => $section_data) { if (isset($section_data['fields']) && is_array($section_data['fields'])) { // Handle regular fields foreach ($section_data['fields'] as $option_key => $field_config) { if (isset($posted_settings[$option_key])) { $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response); } elseif ($field_config['type'] === 'checkbox') { // Checkboxes not in POST are considered unchecked update_option($option_key, 'no'); $response['saved_options'][$option_key] = 'no'; } } } else if (isset($section_data['subsections']) && is_array($section_data['subsections'])) { // Handle sections with subsections (like invoice, quote, email, etc.) foreach ($section_data['subsections'] as $subsection_id => $subsection_data) { if (isset($subsection_data['fields']) && is_array($subsection_data['fields'])) { foreach ($subsection_data['fields'] as $option_key => $field_config) { if (isset($posted_settings[$option_key])) { $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response); } elseif ($field_config['type'] === 'checkbox') { // Checkboxes not in POST are considered unchecked update_option($option_key, 'no'); $response['saved_options'][$option_key] = 'no'; } } } } } } // Fields other code adds to the Tax section for display only // (`easy_invoice_tax_section_additional_fields`) are saved with the // same rules — the reverse-charge switch lived only there and could // never be turned on from the screen. $extra_tax_fields = apply_filters('easy_invoice_tax_section_additional_fields', [], []); $known = []; foreach ($settings_config as $section_data) { foreach ((array) ($section_data['fields'] ?? []) as $k => $unused) { $known[$k] = true; } foreach ((array) ($section_data['subsections'] ?? []) as $sub) { foreach ((array) ($sub['fields'] ?? []) as $k => $unused) { $known[$k] = true; } } } foreach ((array) $extra_tax_fields as $option_key => $field_config) { if (!is_string($option_key) || isset($known[$option_key]) || !is_array($field_config)) { continue; } if (isset($posted_settings[$option_key])) { $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response); } elseif (($field_config['type'] ?? '') === 'checkbox') { update_option($option_key, 'no'); $response['saved_options'][$option_key] = 'no'; } } // Handle special sections like payment methods if (isset($settings_config['payment']) && isset($settings_config['payment']['is_special_section'])) { $this->savePaymentSettings($posted_settings, $response); } // Clear any caches wp_cache_flush(); // Clear settings cache $this->settings_cache = null; self::$shared_settings_cache = null; // Log the save action $this->log('Settings saved successfully by user: ' . get_current_user_id()); } catch (\Exception $e) { $response['success'] = false; $response['message'] = __('Error saving settings: ', 'easy-invoice') . $e->getMessage(); $this->log('Error saving settings: ' . $e->getMessage()); } // Clear settings cache to ensure new fields are recognized $this->clearCache(); // Send JSON response wp_send_json($response); } /** * Sanitize and save a single setting * * @param string $option_key The option key * @param array $field_config The field configuration * @param mixed $value The value to sanitize and save * @param array &$response The response array to update * @return void */ private function sanitizeAndSaveSetting($option_key, $field_config, $value, &$response) { if (in_array($option_key, [ 'easy_invoice_quote_accept_text', 'easy_invoice_quote_accepted_message', 'easy_invoice_quote_declined_message' ])) { } // Unslash the value to prevent double-escaping $value = wp_unslash($value); // Allow pre-sanitization filters $value = apply_filters('easy_invoice_pre_sanitize_option', $value, $option_key, $field_config); // Sanitize based on field type switch ($field_config['type']) { case 'email': $value = sanitize_email($value); break; case 'url': $value = esc_url_raw($value); break; case 'textarea': // Use wp_kses_post for textarea to allow safe HTML while preventing double-escaping $value = wp_kses_post($value); break; case 'wp_editor': // For wp_editor content, use WordPress's built-in sanitization if (is_string($value)) { // Use wp_kses_post which expects unslashed data $value = wp_kses_post($value); } break; case 'number': if (isset($field_config['step']) && strpos((string)$field_config['step'], '.') !== false) { $value = floatval(wp_strip_all_tags(easy_invoice_str_replace(',', '.', $value))); } elseif (isset($field_config['min']) && intval($field_config['min']) < 0) { $value = intval($value); } else { $value = absint($value); } break; case 'checkbox': // For checkboxes, we expect 'yes' or 'no' values $value = ($value === 'yes' || $value === '1' || $value === true) ? 'yes' : 'no'; break; case 'select': $value = sanitize_key($value); // Ensure currency codes are always uppercase if ($option_key === 'easy_invoice_currency_code') { $value = strtoupper($value); } break; case 'multiselect': // For multiselect, ensure it's an array and sanitize each value if (!is_array($value)) { $value = []; } else { $value = array_map('sanitize_key', $value); } break; default: // For regular text fields, use wp_strip_all_tags to prevent double-escaping $value = wp_strip_all_tags($value); break; } // Allow post-sanitization value modification $value = apply_filters("easy_invoice_sanitize_option_{$option_key}", $value, $field_config); $value = apply_filters('easy_invoice_sanitize_option', $value, $option_key, $field_config); // Save the setting normally update_option($option_key, $value); $response['saved_options'][$option_key] = $value; } /** * Get all settings for the plugin * * @param bool $use_cache Whether to use cached settings * @return array The settings array */ public function getSettings($use_cache = true): array { // Return cached settings if available if ($use_cache && is_array($this->settings_cache)) { return $this->settings_cache; } if ($use_cache && is_array(self::$shared_settings_cache)) { return $this->settings_cache = self::$shared_settings_cache; } $settings = []; $config = $this->get_settings_fields_config(); foreach ($config as $section_id => $section_data) { if ($section_id === 'payment' && isset($section_data['gateways'])) { // Handle gateway fields specifically foreach ($section_data['gateways'] as $gateway_id => $gateway_config) { // The per-gateway display name is saved by savePaymentSettings() // but was never loaded back, so the field always showed the default. $display_key = 'easy_invoice_gateway_display_name_' . $gateway_id; $display_val = get_option($display_key, ''); if ('' !== $display_val) { $settings[$display_key] = $display_val; } if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) { foreach ($gateway_config['fields'] as $option_key => $field_data) { $default_value = $field_data['default'] ?? ''; $settings[$option_key] = get_option($option_key, $default_value); } } } } else if (isset($section_data['subsections']) && is_array($section_data['subsections'])) { // Handle sections with subsections (like email, invoice, quote) foreach ($section_data['subsections'] as $subsection_id => $subsection_data) { if (isset($subsection_data['fields']) && is_array($subsection_data['fields'])) { foreach ($subsection_data['fields'] as $option_key => $field_data) { $default_value = $field_data['default'] ?? ''; // Handle special defaults if ($option_key === 'easy_invoice_email_from_name' && $default_value === get_bloginfo('name')) { $default_value = get_bloginfo('name'); } if ($option_key === 'easy_invoice_email_from_address' && $default_value === get_bloginfo('admin_email')) { $default_value = get_bloginfo('admin_email'); } // Check if field has a value_callback function if (isset($field_data['value_callback']) && is_callable($field_data['value_callback'])) { $settings[$option_key] = $field_data['value_callback'](); } else { // Get option value or default $settings[$option_key] = get_option($option_key, $default_value); } } } } } else if (isset($section_data['fields']) && is_array($section_data['fields'])) { foreach ($section_data['fields'] as $option_key => $field_data) { $default_value = $field_data['default'] ?? ''; // Handle special defaults if ($option_key === 'easy_invoice_email_from_name' && $default_value === get_bloginfo('name')) { $default_value = get_bloginfo('name'); } if ($option_key === 'easy_invoice_email_from_address' && $default_value === get_bloginfo('admin_email')) { $default_value = get_bloginfo('admin_email'); } // Check if field has a value_callback function if (isset($field_data['value_callback']) && is_callable($field_data['value_callback'])) { $settings[$option_key] = $field_data['value_callback'](); } else { // Get option value or default $settings[$option_key] = get_option($option_key, $default_value); } } } } // Add special array settings $settings['easy_invoice_payment_methods'] = get_option('easy_invoice_payment_methods', []); $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []); // Cache settings $this->settings_cache = $settings; self::$shared_settings_cache = $settings; // Allow third party to add/modify settings return apply_filters('easy_invoice_settings', $settings); } /** * Log debug information * * @param string $message The message to log * @return void */ protected function log(string $message): void { } /** * Initialize default settings */ public function initializeSettings() { // One-time migration (2.1.22): strip the leftover // "Shortcode: [easy_(invoice|quote)_url …]" // reference line from saved email bodies. Originally intended as admin-side // documentation, but it lived inside the email template and got emailed to // clients verbatim. Guarded by a flag option so each install runs at most once. // // The regex tolerates the optional surrounding

...

because WP's // wp_kses_post may normalise the wrapping when the option is saved. if (!get_option('easy_invoice_email_shortcode_hint_removed_v2')) { foreach (['easy_invoice_invoice_email_body', 'easy_invoice_quote_email_body'] as $opt_key) { $body = get_option($opt_key, ''); if (!is_string($body) || $body === '') { continue; } $cleaned = preg_replace( '#\s*(?:

\s*)?\s*Shortcode\s*:\s*\s*\s*\[easy_(?:invoice|quote)_url[^\]]*\]\s*(?:\s*

)?\s*#i', '', $body ); if ($cleaned !== null && $cleaned !== $body) { update_option($opt_key, $cleaned); } } update_option('easy_invoice_email_shortcode_hint_removed_v2', 1); } // Initialize invoice number settings if not already set if (!get_option('easy_invoice_invoice_prefix')) { update_option('easy_invoice_invoice_prefix', 'INV-'); } if (!get_option('easy_invoice_next_invoice_number')) { update_option('easy_invoice_next_invoice_number', 1); } // Initialize other default settings if (!get_option('easy_invoice_invoice_due_days')) { update_option('easy_invoice_invoice_due_days', 30); } if (!get_option('easy_invoice_currency_code')) { update_option('easy_invoice_currency_code', 'USD'); } if (!get_option('easy_invoice_currency_symbol')) { update_option('easy_invoice_currency_symbol', '$'); } if (!get_option('easy_invoice_currency_position')) { update_option('easy_invoice_currency_position', 'left'); } if (!get_option('easy_invoice_currency_symbol_type')) { update_option('easy_invoice_currency_symbol_type', 'symbol'); } if (!get_option('easy_invoice_decimal_separator')) { update_option('easy_invoice_decimal_separator', '.'); } if (!get_option('easy_invoice_thousands_separator')) { update_option('easy_invoice_thousands_separator', ','); } if (!get_option('easy_invoice_decimal_precision')) { update_option('easy_invoice_decimal_precision', 2); } // Initialize date format setting if (!get_option('easy_invoice_date_format')) { update_option('easy_invoice_date_format', 'us'); } // Fix legacy date format values $current_date_format = get_option('easy_invoice_date_format'); if ($current_date_format === 'mdy' || $current_date_format === 'm/d/Y') { update_option('easy_invoice_date_format', 'us'); } elseif ($current_date_format === 'd/m/Y') { update_option('easy_invoice_date_format', 'uk'); } elseif ($current_date_format === 'Y-m-d') { update_option('easy_invoice_date_format', 'iso'); } // Initialize email settings if (!get_option('easy_invoice_email_from_name')) { update_option('easy_invoice_email_from_name', get_bloginfo('name')); } if (!get_option('easy_invoice_email_from_address')) { update_option('easy_invoice_email_from_address', get_bloginfo('admin_email')); } if (!get_option('easy_invoice_enable_email_styling')) { update_option('easy_invoice_enable_email_styling', 'yes'); } if (!get_option('easy_invoice_bcc_admin')) { update_option('easy_invoice_bcc_admin', 'no'); } if (!get_option('easy_invoice_admin_email')) { update_option('easy_invoice_admin_email', get_option('admin_email')); } // Initialize Text Settings $text_settings = [ 'easy_invoice_text_invoice' => __('Invoice', 'easy-invoice'), 'easy_invoice_text_invoices' => __('Invoices', 'easy-invoice'), 'easy_invoice_text_from' => __('From', 'easy-invoice'), 'easy_invoice_text_to' => __('To', 'easy-invoice'), 'easy_invoice_text_invoice_number' => __('Invoice Number', 'easy-invoice'), 'easy_invoice_text_order_number' => __('Order Number', 'easy-invoice'), 'easy_invoice_text_invoice_date' => __('Invoice Date', 'easy-invoice'), 'easy_invoice_text_due_date' => __('Due Date', 'easy-invoice'), 'easy_invoice_text_total_due' => __('Total Due', 'easy-invoice'), 'easy_invoice_text_qty' => __('Qty', 'easy-invoice'), 'easy_invoice_text_service' => __('Service', 'easy-invoice'), 'easy_invoice_text_rate_price' => __('Rate', 'easy-invoice'), 'easy_invoice_text_adjust' => __('Adjust', 'easy-invoice'), 'easy_invoice_text_sub_total' => __('Sub Total', 'easy-invoice'), 'easy_invoice_text_total' => __('Total', 'easy-invoice'), 'easy_invoice_text_line_total' => __('Total', 'easy-invoice'), 'easy_invoice_text_tax' => __('Tax', 'easy-invoice'), 'easy_invoice_text_discount' => __('Discount', 'easy-invoice'), 'easy_invoice_text_page' => __('Page', 'easy-invoice'), 'easy_invoice_text_print' => __('Print', 'easy-invoice'), 'easy_invoice_text_download_pdf' => __('Download as PDF', 'easy-invoice'), 'easy_invoice_text_send_email' => __('Send Email', 'easy-invoice'), 'easy_invoice_text_pay_now' => __('Pay Now', 'easy-invoice'), 'easy_invoice_text_proceed_payment' => __('Proceed to payment', 'easy-invoice'), 'easy_invoice_text_payment_gateway' => __('Invoice Payment Gateway', 'easy-invoice'), 'easy_invoice_text_quote' => __('Quote', 'easy-invoice'), 'easy_invoice_text_quotes' => __('Quotes', 'easy-invoice'), 'easy_invoice_text_quote_number' => __('Quote Number', 'easy-invoice'), 'easy_invoice_text_accept_quote' => __('Accept Quote', 'easy-invoice'), 'easy_invoice_text_decline_quote' => __('Decline Quote', 'easy-invoice'), 'easy_invoice_text_decline_reason' => __('Reason for declining', 'easy-invoice'), 'easy_invoice_text_quote_amount' => __('Quote Amount', 'easy-invoice'), 'easy_invoice_text_valid_until' => __('Valid Until Date', 'easy-invoice'), 'easy_invoice_text_quote_date' => __('Quote Date', 'easy-invoice'), 'easy_invoice_text_available' => __('Available', 'easy-invoice'), 'easy_invoice_text_draft' => __('Draft', 'easy-invoice'), 'easy_invoice_text_overdue' => __('Overdue', 'easy-invoice'), 'easy_invoice_text_paid' => __('Paid', 'easy-invoice'), 'easy_invoice_text_unpaid' => __('Unpaid', 'easy-invoice'), 'easy_invoice_text_cancelled' => __('Cancelled', 'easy-invoice'), ]; foreach ($text_settings as $option_key => $default_value) { if (!get_option($option_key)) { update_option($option_key, $default_value); } } if (!get_option('easy_invoice_invoice_email_enabled')) { update_option('easy_invoice_invoice_email_enabled', 'yes'); } if (!get_option('easy_invoice_invoice_email_subject')) { update_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice')); } if (!get_option('easy_invoice_invoice_email_body')) { update_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice')); } if (!get_option('easy_invoice_quote_email_enabled')) { update_option('easy_invoice_quote_email_enabled', 'yes'); } if (!get_option('easy_invoice_quote_email_subject')) { update_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice')); } if (!get_option('easy_invoice_quote_email_body')) { update_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote')); } if (!get_option('easy_invoice_payment_email_enabled')) { update_option('easy_invoice_payment_email_enabled', 'yes'); } if (!get_option('easy_invoice_payment_email_subject')) { update_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice')); } if (!get_option('easy_invoice_payment_email_body')) { update_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment')); } } /** * Test email functionality */ public function testEmail(): void { // Verify nonce if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) { wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]); } // Check permissions if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]); } // Get test email address $test_email = sanitize_email($_POST['test_email'] ?? ''); if (empty($test_email)) { wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]); } // Test email sending $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); $result = $email_manager->testEmail($test_email); if ($result['success']) { wp_send_json_success($result); } else { wp_send_json_error($result); } } /** * Test payment reminder email functionality */ public function testPaymentReminderEmail(): void { // Verify nonce if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) { wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]); } // Check permissions if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]); } // Get test email address $test_email = sanitize_email($_POST['test_email'] ?? ''); if (empty($test_email)) { wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]); } // Get payment reminder settings $subject = get_option('easy_invoice_pro_payment_reminder_subject', __('A friendly reminder - Invoice #{{invoice_number}}', 'easy-invoice')); $body = get_option('easy_invoice_pro_payment_reminder_body', ''); if (empty($body)) { wp_send_json_error(['message' => __('Payment reminder email template is empty. Please configure the email message first.', 'easy-invoice')]); } // Create sample data for testing $sample_data = [ 'invoice_number' => 'TEST-001', 'client_name' => 'Test Client', 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'), 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')), 'total_amount' => '$1,000.00', 'amount_due' => '$1,000.00', 'due_date' => wp_date('Y-m-d', strtotime('+7 days')), ]; // Replace placeholders foreach ($sample_data as $placeholder => $value) { $subject = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $subject); $body = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $body); } // Test email sending $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); $result = $email_manager->sendTestTemplateEmail($test_email, $subject, $body); if ($result['success']) { wp_send_json_success($result); } else { wp_send_json_error($result); } } /** * Test template email functionality */ public function testTemplateEmail(): void { // Verify nonce if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) { wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]); } // Check permissions if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]); } // Get test email address and template type $test_email = sanitize_email($_POST['test_email'] ?? ''); $template_type = sanitize_text_field($_POST['template_type'] ?? ''); if (empty($test_email)) { wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]); } if (empty($template_type)) { wp_send_json_error(['message' => __('Template type is required', 'easy-invoice')]); } // Get the actual configured email settings $subject = ''; $body = ''; switch ($template_type) { case 'invoice_available': $subject = get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice')); $body = get_option('easy_invoice_invoice_email_body', self::getInvoiceEmailBody()); break; case 'quote_available': $subject = get_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice')); $body = get_option('easy_invoice_quote_email_body', self::getQuoteEmailBody()); break; case 'payment_received': $subject = get_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice')); $body = get_option('easy_invoice_payment_email_body', self::getPaymentEmailBody()); break; default: wp_send_json_error(['message' => __('Invalid template type', 'easy-invoice')]); } // Ensure we have valid strings $subject = is_string($subject) ? $subject : ''; $body = is_string($body) ? $body : ''; if (empty($subject) || empty($body)) { wp_send_json_error(['message' => __('Email template is empty or invalid', 'easy-invoice')]); } // Create sample data for testing $sample_data = [ 'invoice_number' => 'TEST-001', 'quote_number' => 'TEST-Q-001', 'client_name' => 'Test Client', 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'), 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')), 'total_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00', 'payment_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00', 'due_date' => wp_date('Y-m-d', strtotime('+30 days')), 'expiry_date' => wp_date('Y-m-d', strtotime('+30 days')), 'payment_date' => current_time('Y-m-d'), 'payment_method' => 'Credit Card', 'transaction_id' => 'TXN-' . uniqid(), 'payment_terms' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')), // Everything the stock templates can mention, so a test never shows a raw tag. 'amount_due' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00', 'subtotal' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00', 'tax_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(0) : '$0.00', 'discount_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(0) : '$0.00', 'issue_date' => current_time('Y-m-d'), 'invoice_title' => __('Sample invoice', 'easy-invoice'), 'quote_title' => __('Sample quote', 'easy-invoice'), 'invoice_url' => home_url('/'), 'quote_url' => home_url('/'), 'payment_url' => home_url('/'), 'site_url' => home_url('/'), 'admin_url' => admin_url(), 'client_email' => 'client@example.com', 'client_first_name' => 'Test', 'client_last_name' => 'Client', 'client_address' => '1 Example Street', 'company_phone' => get_option('easy_invoice_company_phone', ''), 'company_address' => get_option('easy_invoice_company_address', ''), 'company_website' => get_option('easy_invoice_company_website', ''), ]; // Ensure all sample data values are strings foreach ($sample_data as $key => $value) { $sample_data[$key] = is_string($value) ? $value : (string) $value; } // Replace placeholders in subject and body foreach ($sample_data as $placeholder => $value) { $subject = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $subject); $body = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $body); } // Test email sending $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); $result = $email_manager->sendTestTemplateEmail($test_email, $subject, $body); if ($result['success']) { wp_send_json_success($result); } else { wp_send_json_error($result); } } /** * Check if quote adjust field should be shown * * @return bool True if adjust field should be shown */ public static function shouldShowQuoteAdjustField(): bool { return get_option('easy_invoice_quote_show_adjust_field', 'yes') === 'yes'; } /** * Check if invoice adjust field should be shown * * @return bool True if adjust field should be shown */ public static function shouldShowInvoiceAdjustField(): bool { return get_option('easy_invoice_invoice_show_adjust_field', 'yes') === 'yes'; } /** * Get invoice prefix * * @return string Invoice prefix */ public static function getInvoicePrefix(): string { return get_option('easy_invoice_invoice_prefix', 'EIN_'); } /** * Get invoice starting number * * @return int Invoice starting number */ public static function getInvoiceStartingNumber(): int { return (int) get_option('easy_invoice_invoice_starting_number', 0); } /** * Get invoice terms and conditions * * @return string Invoice terms and conditions */ public static function getInvoiceTermsConditions(): string { return get_option('easy_invoice_invoice_terms_conditions', __('Payment is due within 30 days from date of invoice', 'easy-invoice')); } /** * Get invoice footer text * * @return string Invoice footer text */ public static function getInvoiceFooterText(): string { return get_option('easy_invoice_invoice_footer_text', ''); } /** * Get quote prefix * * @return string Quote prefix */ public static function getQuotePrefix(): string { return get_option('easy_invoice_quote_prefix', 'EIQN_'); } /** * Get quote starting number * * @return int Quote starting number */ public static function getQuoteStartingNumber(): int { return (int) get_option('easy_invoice_quote_starting_number', 1); } /** * Get quote terms and conditions * * @return string Quote terms and conditions */ public static function getQuoteTermsConditions(): string { return get_option('easy_invoice_quote_terms_conditions', __('This quote has a fixed price. Upon acceptance, we kindly ask for a 25% deposit prior to initiating the work.', 'easy-invoice')); } /** * Get quote footer text * * @return string Quote footer text */ public static function getQuoteFooterText(): string { return get_option('easy_invoice_quote_footer_text', __('Thank you for your business.', 'easy-invoice')); } /** * Check if quote accept button should be shown * * @return bool True if accept button should be shown */ public static function shouldShowQuoteAcceptButton(): bool { return get_option('easy_invoice_quote_accept_button', 'yes') === 'yes'; } /** * Get quote accept action * * @return string Quote accept action */ public static function getQuoteAcceptAction(): string { return get_option('easy_invoice_quote_accept_action', 'convert'); } /** * Get quote accept text * * @return string Quote accept text */ public static function getQuoteAcceptText(): string { return get_option('easy_invoice_quote_accept_text', __('Important: When you accept this Quote, an Invoice will be created automatically. This will form a legally binding contract.', 'easy-invoice')); } /** * Get accepted quote message * * @return string Accepted quote message */ public static function getAcceptedQuoteMessage(): string { return get_option('easy_invoice_quote_accepted_message', __('You\'ve confirmed the Quote.
We\'ll get in touch with you shortly.', 'easy-invoice')); } /** * Check if decline reason is required * * @return bool True if decline reason is required */ public static function isDeclineReasonRequired(): bool { return get_option('easy_invoice_quote_decline_reason_required', 'no') === 'yes'; } /** * Get declined quote message * * @return string Declined quote message */ public static function getDeclinedQuoteMessage(): string { return get_option('easy_invoice_quote_declined_message', ''); } // Email Settings Helper Functions /** * Check if invoice available email is enabled * * @return bool True if invoice email is enabled */ public static function isInvoiceEmailEnabled(): bool { return get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes'; } /** * Get invoice email subject * * @return string Invoice email subject */ public static function getInvoiceEmailSubject(): string { return get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice')); } /** * Get invoice email body * * @return string Invoice email body */ public static function getInvoiceEmailBody(): string { return get_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice')); } /** * Check if quote available email is enabled * * @return bool True if quote email is enabled */ public static function isQuoteEmailEnabled(): bool { return get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes'; } /** * Get quote email subject * * @return string Quote email subject */ public static function getQuoteEmailSubject(): string { return get_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice')); } /** * Get quote email body * * @return string Quote email body */ public static function getQuoteEmailBody(): string { return get_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote')); } /** * Check if payment received email is enabled * * @return bool True if payment email is enabled */ public static function isPaymentEmailEnabled(): bool { return get_option('easy_invoice_payment_email_enabled', 'yes') === 'yes'; } /** * Get payment email subject * * @return string Payment email subject */ public static function getPaymentEmailSubject(): string { return get_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice')); } /** * Get payment email body * * @return string Payment email body */ public static function getPaymentEmailBody(): string { return get_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment')); } // ======================================== // TEXT SETTINGS HELPER FUNCTIONS // ======================================== // // USAGE IN TEMPLATES: // Instead of: echo __('Invoice', 'easy-invoice'); // Use: echo \EasyInvoice\Controllers\SettingsController::getTextInvoice(); // // Example: //

echo \EasyInvoice\Controllers\SettingsController::getTextInvoice();

//

echo \EasyInvoice\Controllers\SettingsController::getTextFrom(); : echo $company_name;

//

echo \EasyInvoice\Controllers\SettingsController::getTextTo(); : echo $client_name;

// /** * Get custom text setting with fallback to default * * @param string $key Text setting key * @param string $default Default text * @return string Custom text or default */ /** * Every document label the site can override, with its saved value. * * The getText*() methods below are what templates call; this list exists * for anything that needs the whole set at once -- the Pro Translations * page offers each saved label for translation. * * @return array keyed by setting key. */ public static function getTextLabels(): array { $labels = [ 'invoice' => __('Invoice', 'easy-invoice'), 'invoices' => __('Invoices', 'easy-invoice'), 'to' => __('To', 'easy-invoice'), 'from' => __('From', 'easy-invoice'), 'invoice_number' => __('Invoice Number', 'easy-invoice'), 'invoice_date' => __('Invoice Date', 'easy-invoice'), 'due_date' => __('Due Date', 'easy-invoice'), 'total_due' => __('Total Due', 'easy-invoice'), 'qty' => __('Qty', 'easy-invoice'), 'service' => __('Service', 'easy-invoice'), 'rate_price' => __('Rate', 'easy-invoice'), 'adjust' => __('Adjust', 'easy-invoice'), 'sub_total' => __('Sub Total', 'easy-invoice'), 'total' => __('Total', 'easy-invoice'), 'tax' => __('Tax', 'easy-invoice'), 'discount' => __('Discount', 'easy-invoice'), 'print' => __('Print', 'easy-invoice'), 'download_pdf' => __('Download as PDF', 'easy-invoice'), 'send_email' => __('Send Email', 'easy-invoice'), 'pay_now' => __('Pay Now', 'easy-invoice'), 'quote' => __('Quote', 'easy-invoice'), 'quote_number' => __('Quote Number', 'easy-invoice'), 'quote_date' => __('Quote Date', 'easy-invoice'), 'valid_until' => __('Valid Until Date', 'easy-invoice'), 'accept_quote' => __('Accept Quote', 'easy-invoice'), 'decline_quote' => __('Decline Quote', 'easy-invoice'), 'decline_reason' => __('Reason for declining', 'easy-invoice'), ]; $out = []; foreach ($labels as $key => $default) { $out[$key] = [ 'label' => ucfirst(str_replace('_', ' ', $key)), 'default' => $default, 'value' => (string) get_option('easy_invoice_text_' . $key, $default), ]; } return $out; } public static function getTextSetting(string $key, string $default): string { $option_key = 'easy_invoice_text_' . $key; $value = (string) get_option($option_key, $default); /** * Filter a document label after the saved override is applied. * * Saved labels are literal text and skip gettext, so anything that * renders a document in another language (Pro's Client Language) uses * this to run them through the active locale. * * @param string $value Label to show. * @param string $key Setting key, e.g. 'total'. * @param string $default Translated default. */ return (string) apply_filters('easy_invoice_text_setting', $value, $key, $default); } // Invoice Text Settings public static function getTextInvoice(): string { return self::getTextSetting('invoice', __('Invoice', 'easy-invoice')); } public static function getTextInvoices(): string { return self::getTextSetting('invoices', __('Invoices', 'easy-invoice')); } public static function getTextTo(): string { return self::getTextSetting('to', __('To', 'easy-invoice')); } public static function getTextInvoiceNumber(): string { return self::getTextSetting('invoice_number', __('Invoice Number', 'easy-invoice')); } public static function getTextInvoiceDate(): string { return self::getTextSetting('invoice_date', __('Invoice Date', 'easy-invoice')); } public static function getTextDueDate(): string { return self::getTextSetting('due_date', __('Due Date', 'easy-invoice')); } public static function getTextTotalDue(): string { return self::getTextSetting('total_due', __('Total Due', 'easy-invoice')); } public static function getTextQty(): string { return self::getTextSetting('qty', __('Qty', 'easy-invoice')); } public static function getTextService(): string { return self::getTextSetting('service', __('Service', 'easy-invoice')); } public static function getTextRatePrice(): string { return self::getTextSetting('rate_price', __('Rate', 'easy-invoice')); } public static function getTextAdjust(): string { return self::getTextSetting('adjust', __('Adjust', 'easy-invoice')); } public static function getTextSubTotal(): string { return self::getTextSetting('sub_total', __('Sub Total', 'easy-invoice')); } public static function getTextTotal(): string { return self::getTextSetting('total', __('Total', 'easy-invoice')); } /** Heading of the line-item amount column, separate from the grand total. */ public static function getTextLineTotal(): string { return self::getTextSetting('line_total', __('Total', 'easy-invoice')); } public static function getTextTax(): string { return self::getTextSetting('tax', __('Tax', 'easy-invoice')); } public static function getTextDiscount(): string { return self::getTextSetting('discount', __('Discount', 'easy-invoice')); } public static function getTextPrint(): string { return self::getTextSetting('print', __('Print', 'easy-invoice')); } public static function getTextDownloadPdf(): string { return self::getTextSetting('download_pdf', __('Download as PDF', 'easy-invoice')); } public static function getTextSendEmail(): string { return self::getTextSetting('send_email', __('Send Email', 'easy-invoice')); } public static function getTextPayNow(): string { return self::getTextSetting('pay_now', __('Pay Now', 'easy-invoice')); } // Quote Text Settings public static function getTextQuote(): string { return self::getTextSetting('quote', __('Quote', 'easy-invoice')); } public static function getTextQuoteNumber(): string { return self::getTextSetting('quote_number', __('Quote Number', 'easy-invoice')); } public static function getTextAcceptQuote(): string { return self::getTextSetting('accept_quote', __('Accept Quote', 'easy-invoice')); } public static function getTextDeclineQuote(): string { return self::getTextSetting('decline_quote', __('Decline Quote', 'easy-invoice')); } public static function getTextDeclineReason(): string { return self::getTextSetting('decline_reason', __('Reason for declining', 'easy-invoice')); } public static function getTextValidUntil(): string { return self::getTextSetting('valid_until', __('Valid Until Date', 'easy-invoice')); } public static function getTextQuoteDate(): string { return self::getTextSetting('quote_date', __('Quote Date', 'easy-invoice')); } public static function getTextFrom(): string { return self::getTextSetting('from', __('From', 'easy-invoice')); } /** * Get the actual date format string from the stored format identifier * * @param string $format_identifier The format identifier (us, uk, iso) * @return string The actual date format string */ public static function getDateFormatString($format_identifier = null): string { if ($format_identifier === null) { $format_identifier = get_option('easy_invoice_date_format', 'us'); } switch ($format_identifier) { case 'uk': return 'd/m/Y'; case 'iso': return 'Y-m-d'; case 'us': default: return 'm/d/Y'; } } /** * Get the current date format string * * @return string The current date format string */ public static function getCurrentDateFormat(): string { return self::getDateFormatString(); } /** * Format a date using the Easy Invoice date format setting * * @param string|int $date The date to format (timestamp or date string) * @return string The formatted date */ public static function formatDate($date): string { $format = self::getCurrentDateFormat(); $timestamp = is_numeric($date) ? $date : strtotime($date); return date_i18n($format, $timestamp); } /** * AJAX handler for regenerating invoice numbers */ public function ajaxRegenerateInvoiceNumbers() { // Verify nonce $nonce = $_POST['nonce'] ?? $_POST['easy_invoice_settings_nonce'] ?? ''; if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) { wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]); } // Check permissions if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]); } try { // Get the current next invoice number $current_next_number = intval(get_option('easy_invoice_next_invoice_number', 1)); $prefix = get_option('easy_invoice_invoice_prefix', 'EIIN_'); // Start regeneration from the current next number $starting_number = $current_next_number; // Get all invoices ordered by creation date $invoices = get_posts([ 'post_type' => 'easy_invoice', 'post_status' => 'publish', 'numberposts' => -1, 'orderby' => 'date', 'order' => 'ASC' ]); if (empty($invoices)) { wp_send_json_success([ 'message' => __('No invoices found to regenerate', 'easy-invoice'), 'regenerated_count' => 0 ]); } $regenerated_count = 0; $current_number = $starting_number; foreach ($invoices as $invoice) { // Generate new invoice number $new_invoice_number = $prefix . str_pad($current_number, 6, '0', STR_PAD_LEFT); // Update the invoice number update_post_meta($invoice->ID, '_easy_invoice_number', $new_invoice_number); $regenerated_count++; $current_number++; } // Update the next invoice number counter to continue from the last regenerated number + 1 update_option('easy_invoice_next_invoice_number', $current_number); wp_send_json_success([ /* translators: %d: number regenerated. */ 'message' => sprintf(__('Successfully regenerated %d invoice numbers', 'easy-invoice'), $regenerated_count), 'regenerated_count' => $regenerated_count, 'next_number' => $current_number ]); } catch (Exception $e) { wp_send_json_error(['message' => __('Failed to regenerate invoice numbers', 'easy-invoice')]); } } /** * AJAX handler for regenerating quote numbers */ public function ajaxRegenerateQuoteNumbers() { // Verify nonce $nonce = $_POST['nonce'] ?? $_POST['easy_invoice_settings_nonce'] ?? ''; if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) { wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]); } // Check permissions if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]); } try { $current_next_number = intval(get_option('easy_invoice_next_quote_number', 1)); $prefix = get_option('easy_invoice_quote_prefix', 'QT-'); $starting_number = $current_next_number; $quotes = get_posts([ 'post_type' => 'easy_invoice_quote', 'post_status' => 'publish', 'numberposts' => -1, 'orderby' => 'date', 'order' => 'ASC', 'meta_query' => [ [ 'key' => '_easy_invoice_quote_number', 'compare' => 'EXISTS' ] ] ]); $regenerated_count = 0; $current_number = $starting_number; foreach ($quotes as $quote_post) { $new_quote_number = $prefix . str_pad($current_number, 6, '0', STR_PAD_LEFT); update_post_meta($quote_post->ID, '_easy_invoice_quote_number', $new_quote_number); $regenerated_count++; $current_number++; } update_option('easy_invoice_next_quote_number', $current_number); wp_send_json_success([ /* translators: %d: number regenerated. */ 'message' => sprintf(__('Successfully regenerated %d quote numbers', 'easy-invoice'), $regenerated_count), 'regenerated_count' => $regenerated_count, 'next_number' => $current_number ]); } catch (Exception $e) { wp_send_json_error(['message' => __('Failed to regenerate quote numbers', 'easy-invoice')]); } } }