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_easy_invoice_add_offline_method', [$this, 'ajaxAddOfflineMethod']);
add_action('wp_ajax_easy_invoice_remove_offline_method', [$this, 'ajaxRemoveOfflineMethod']);
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 after due date)', 'easy-invoice'), 'type' => 'number', 'default' => 3, 'min' => 0, 'col_span' => 'sm:col-span-3', 'description' => __('Emails the Reminder template once to each invoice still owed this many days after its due date. Leave empty to switch reminders off. Easy Invoice Pro replaces this with its own schedule.', 'easy-invoice')],
'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
}
}
/**
* Settings → Payment Methods → "Add an offline method": registers a new
* offline payment method (ACH, wire transfer, mobile money, …) by name.
*/
public function ajaxAddOfflineMethod(): void {
check_ajax_referer('easy_invoice_settings', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => __('You do not have permission to change payment methods.', 'easy-invoice')]);
}
$title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : '';
$id = \EasyInvoice\Services\OfflinePayments::addCustom($title);
if (is_wp_error($id)) {
wp_send_json_error(['message' => $id->get_error_message()]);
}
wp_send_json_success([
'id' => $id,
/* translators: %s: method name. */
'message' => sprintf(__('"%s" added. Fill in its instructions below and save.', 'easy-invoice'), $title),
]);
}
/**
* Remove a merchant-added offline method. Recorded payments keep the method name.
*/
public function ajaxRemoveOfflineMethod(): void {
check_ajax_referer('easy_invoice_settings', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => __('You do not have permission to change payment methods.', 'easy-invoice')]);
}
$id = isset($_POST['id']) ? sanitize_key(wp_unslash($_POST['id'])) : '';
if ('' === $id || !\EasyInvoice\Services\OfflinePayments::removeCustom($id)) {
wp_send_json_error(['message' => __('That payment method cannot be removed.', 'easy-invoice')]);
}
wp_send_json_success(['message' => __('Payment method removed.', 'easy-invoice')]);
}
/**
* 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*
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