PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.7
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.7
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / includes / Controllers / SettingsController.php

SettingsController.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.3.7, at includes/Controllers/SettingsController.php

2,276 lines 102.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Settings Controller Class
4 *
5 * Handles configuration, display and storage of all plugin settings
6 *
7 * @package Easy_Invoice
8 * @subpackage Controllers
9 * @since 1.0.0
10 */
11
12 namespace EasyInvoice\Controllers;
13
14 use EasyInvoice\Constants\PagesSlugs;
15 use EasyInvoice\Helpers\CurrencyHelper;
16
17 /**
18 * SettingsController handles all settings-related functionality
19 */
20 class SettingsController extends BaseController {
21 /**
22 * Settings cache
23 *
24 * @var array
25 */
26 private $settings_cache = null;
27
28 /**
29 * Settings configuration cache
30 *
31 * @var array
32 */
33 private $config_cache = null;
34
35 /**
36 * Clear settings cache
37 *
38 * @return void
39 */
40 public function clearCache(): void {
41 $this->config_cache = null;
42 // Also clear any WordPress object cache
43 wp_cache_delete('easy_invoice_settings_config', 'easy_invoice');
44 // Force clear any other caches
45 if (function_exists('wp_cache_flush')) {
46 wp_cache_flush();
47 }
48 }
49
50 /**
51 * Initialize the controller
52 *
53 * @return void
54 */
55 public function init() {
56 // Add AJAX handlers
57 add_action('wp_ajax_easy_invoice_save_settings', [$this, 'saveSettings']);
58 add_action('wp_ajax_easy_invoice_test_email', [$this, 'testEmail']);
59 add_action('wp_ajax_easy_invoice_test_template_email', [$this, 'testTemplateEmail']);
60 add_action('wp_ajax_easy_invoice_test_payment_reminder_email', [$this, 'testPaymentReminderEmail']);
61 add_action('wp_ajax_regenerate_invoice_numbers', [$this, 'ajaxRegenerateInvoiceNumbers']);
62 add_action('wp_ajax_regenerate_quote_numbers', [$this, 'ajaxRegenerateQuoteNumbers']);
63
64 // Clear cache on admin init to ensure new fields are recognized
65 add_action('admin_init', [$this, 'clearCache']);
66
67 // Enqueue admin scripts
68 add_action('admin_enqueue_scripts', [$this, 'enqueueAdminScripts']);
69
70 // Initialize settings
71 $this->initializeSettings();
72 }
73
74
75
76 /**
77 * Enqueue admin scripts and styles for the settings page.
78 *
79 * @param string $hook_suffix The current admin page.
80 * @return void
81 */
82 public function enqueueAdminScripts($hook_suffix) {
83 // Check if we are on the Easy Invoice settings page.
84 if (empty($hook_suffix) || strpos($hook_suffix, PagesSlugs::SETTINGS) === false) {
85 return;
86 }
87
88 wp_enqueue_script('jquery-ui-sortable');
89 wp_enqueue_media(); // For the logo uploader
90
91 // Enqueue Select2 for enhancing multiselect fields
92 wp_enqueue_style(
93 'select2',
94 EASY_INVOICE_URL . 'assets/lib/select2/select2.min.css',
95 [],
96 '4.0.13'
97 );
98
99
100 wp_enqueue_script(
101 'select2',
102 EASY_INVOICE_URL . 'assets/lib/select2/select2.min.js',
103 ['jquery'],
104 '4.0.13',
105 true
106 );
107
108 // Enqueue confirmation modal for premium features
109 wp_enqueue_script(
110 'easy-invoice-confirmation-modal',
111 EASY_INVOICE_URL . 'assets/js/confirmation-modal.js',
112 ['jquery'],
113 EASY_INVOICE_VERSION,
114 true
115 );
116
117 // Enqueue toast notification system
118 wp_enqueue_script(
119 'easy-invoice-toast',
120 EASY_INVOICE_URL . 'assets/js/easy-invoice-toast.js',
121 ['jquery'],
122 EASY_INVOICE_VERSION,
123 true
124 );
125
126 $script_handle = 'easy-invoice-settings';
127 wp_enqueue_script(
128 $script_handle,
129 EASY_INVOICE_URL . 'assets/js/settings.js',
130 ['jquery', 'jquery-ui-sortable', 'wp-util', 'select2', 'easy-invoice-confirmation-modal', 'media-views'],
131 EASY_INVOICE_VERSION,
132 true
133 );
134
135 // Enqueue the new settings.css only on the settings page
136 wp_enqueue_style(
137 'easy-invoice-settings',
138 EASY_INVOICE_URL . 'assets/css/settings.css',
139 [],
140 EASY_INVOICE_VERSION
141 );
142
143 $localize_data = apply_filters('easy_invoice_settings_js_data', [
144 'ajaxurl' => admin_url('admin-ajax.php'),
145 'nonce' => wp_create_nonce('easy_invoice_settings'),
146 'saveSuccess' => __('Settings saved successfully', 'easy-invoice'),
147 'saveError' => __('Error saving settings', 'easy-invoice'),
148 ]);
149
150 wp_localize_script($script_handle, 'easyInvoiceSettings', $localize_data);
151
152 do_action('easy_invoice_after_admin_scripts', $hook_suffix);
153 }
154
155 /**
156 * Check if Easy Invoice Pro is active
157 *
158 * @return bool
159 */
160 private function isProActive(): bool {
161 return easy_invoice_has_pro();
162 }
163
164 /**
165 * Get settings fields configuration
166 *
167 * @return array
168 */
169 public function get_settings_fields_config() {
170 // Use cached config if available
171 if (is_array($this->config_cache)) {
172 return $this->config_cache;
173 }
174
175 $config = [
176 'company' => [
177 'title' => __('Company Information', 'easy-invoice'),
178 'description' => __('Set up your company details', 'easy-invoice'),
179 'icon' => 'fas fa-building',
180 'fields' => [
181 'easy_invoice_company_name' => ['label' => __('Company Name', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6' ],
182 'easy_invoice_company_email' => ['label' => __('Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-6' ],
183 'easy_invoice_company_phone' => ['label' => __('Phone Number', 'easy-invoice'), 'type' => 'tel', 'default' => '', 'col_span' => 'sm:col-span-6' ],
184 'easy_invoice_company_website' => ['label' => __('Website', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6' ],
185 'easy_invoice_company_address' => ['label' => __('Address', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6' ],
186 'easy_invoice_tax_number' => ['label' => __('Tax ID / VAT Number', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6' ],
187 'easy_invoice_company_logo' => ['label' => __('Company Logo', 'easy-invoice'), 'type' => 'image', 'default' => '', 'col_span' => 'sm:col-span-6' ],
188 ]
189 ],
190 'invoice' => [
191 'title' => __('Invoice Settings', 'easy-invoice'),
192 'description' => __('Configure invoice-specific settings and preferences', 'easy-invoice'),
193 'icon' => 'fas fa-file-invoice',
194 'fields' => [
195 'easy_invoice_invoice_prefix' => [
196 'label' => __('Invoice Prefix', 'easy-invoice'),
197 'type' => 'text',
198 'default' => 'EIIN_',
199 'description' => __('Prefix for invoice numbers (e.g., EIIN_, INV-)', 'easy-invoice'),
200 'col_span' => 'sm:col-span-2'
201 ],
202 'easy_invoice_next_invoice_number' => [
203 'label' => __('Next Invoice Number', 'easy-invoice'),
204 'type' => 'number',
205 'default' => '1',
206 'description' => __('Set the next invoice number to be generated. This will be the number of the next invoice created.', 'easy-invoice'),
207 'col_span' => 'sm:col-span-2',
208 ],
209 'easy_invoice_regenerate_invoice_numbers' => [
210 'label' => __('Regenerate Invoice Numbers', 'easy-invoice'),
211 'type' => 'button',
212 'button_text' => __('Regenerate All Invoice Numbers', 'easy-invoice'),
213 'button_class' => 'button button-secondary',
214 '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'),
215 'col_span' => 'sm:col-span-2',
216 'ajax_action' => 'regenerate_invoice_numbers'
217 ],
218 'easy_invoice_invoice_show_adjust_field' => [
219 'label' => __('Show/Hide Adjust Field', 'easy-invoice'),
220 'type' => 'checkbox',
221 'default' => 'yes',
222 'description' => __('Enable/Disable Adjust field. Tick this to show adjust field', 'easy-invoice'),
223 'col_span' => 'sm:col-span-6'
224 ],
225 'easy_invoice_invoice_terms_conditions' => [
226 'label' => __('Terms & Conditions', 'easy-invoice'),
227 'type' => 'wp_editor',
228 'default' => __('Payment is due within 30 days from date of invoice', 'easy-invoice'),
229 'description' => __('Terms and conditions that will be displayed on your invoice', 'easy-invoice'),
230 'col_span' => 'sm:col-span-6'
231 ],
232 'easy_invoice_invoice_footer_text' => [
233 'label' => __('Footer Text', 'easy-invoice'),
234 'type' => 'wp_editor',
235 'default' => '',
236 'description' => __('You can modify your invoice footer text from here. HTML tags supports: a, br, em, strong, hr, p, h1 to h4', 'easy-invoice'),
237 'col_span' => 'sm:col-span-6'
238 ],
239 ],
240 ],
241 'quote' => [
242 'title' => __('Quote Settings', 'easy-invoice'),
243 'description' => __('Configure quote-specific settings and preferences', 'easy-invoice'),
244 'icon' => 'fas fa-file-contract',
245 'fields' => [
246 'easy_invoice_quote_prefix' => [
247 'label' => __('Quote Prefix', 'easy-invoice'),
248 'type' => 'text',
249 'default' => 'EIQN_',
250 'description' => __('Prefix for quote numbers (e.g., EIQN_, QT-)', 'easy-invoice'),
251 'col_span' => 'sm:col-span-2'
252 ],
253 'easy_invoice_next_quote_number' => [
254 'label' => __('Next Quote Number', 'easy-invoice'),
255 'type' => 'number',
256 'default' => '1',
257 'description' => __('Set the next quote number to be generated. This will be the number of the next quote created.', 'easy-invoice'),
258 'col_span' => 'sm:col-span-2',
259 ],
260 'easy_invoice_regenerate_quote_numbers' => [
261 'label' => __('Regenerate Quote Numbers', 'easy-invoice'),
262 'type' => 'button',
263 'button_text' => __('Regenerate All Quote Numbers', 'easy-invoice'),
264 'button_class' => 'button button-secondary',
265 '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'),
266 'col_span' => 'sm:col-span-2',
267 'ajax_action' => 'regenerate_quote_numbers'
268 ],
269 'easy_invoice_quote_show_adjust_field' => [
270 'label' => __('Show/Hide Adjust Field', 'easy-invoice'),
271 'type' => 'checkbox',
272 'default' => 'yes',
273 'description' => __('Enable/Disable Adjust field. Tick this to show adjust field', 'easy-invoice'),
274 'col_span' => 'sm:col-span-6'
275 ],
276 'easy_invoice_quote_terms_conditions' => [
277 'label' => __('Terms & Conditions', 'easy-invoice'),
278 'type' => 'wp_editor',
279 'default' => __('This quote has a fixed price. Upon acceptance, we kindly ask for a 25% deposit prior to initiating the work.', 'easy-invoice'),
280 'description' => __('Terms and conditions that will be displayed on your quote!', 'easy-invoice'),
281 'col_span' => 'sm:col-span-6'
282 ],
283 'easy_invoice_quote_footer_text' => [
284 'label' => __('Footer Text', 'easy-invoice'),
285 'type' => 'wp_editor',
286 'default' => __('Thanks for choosing Easy Invoice', 'easy-invoice'),
287 'description' => __('You can modify your quote footer text from here. HTML tags supports: a, br, em, strong, hr, p, h1 to h4', 'easy-invoice'),
288 'col_span' => 'sm:col-span-6'
289 ],
290 'easy_invoice_quote_accept_button' => [
291 'label' => __('Accept quote button', 'easy-invoice'),
292 'type' => 'checkbox',
293 'default' => 'yes',
294 'description' => __('Show/hide accept quote button on quotes.', 'easy-invoice'),
295 'col_span' => 'sm:col-span-6'
296 ],
297 'easy_invoice_quote_accept_action' => [
298 'label' => __('Accept quote button action', 'easy-invoice'),
299 'type' => 'select',
300 'default' => 'convert',
301 'options' => [
302 'convert' => __('Convert quote to invoice - Draft', 'easy-invoice'),
303 'convert_available' => __('Convert quote to invoice - Available', 'easy-invoice'),
304 'convert_send' => __('Convert quote to invoice and send to client - Available', 'easy-invoice'),
305 'duplicate' => __('Create new invoice, keep quote as-is - Draft', 'easy-invoice'),
306 'duplicate_send' => __('Create new invoice and send to client, keep quote as-is - Available', 'easy-invoice'),
307 'do_nothing' => __('Do nothing', 'easy-invoice'),
308 ],
309 'description' => __('Upon the client clicking the "accept quote" button, the subsequent action will be activated.', 'easy-invoice'),
310 'col_span' => 'sm:col-span-6'
311 ],
312 'easy_invoice_quote_accept_text' => [
313 'label' => __('Accept Quote Text', 'easy-invoice'),
314 'type' => 'wp_editor',
315 'default' => __('Important: When you accept this Quote, an Invoice will be created automatically. This will form a legally binding contract.', 'easy-invoice'),
316 'description' => __('This information tells your client what happens once they accept the Quote', 'easy-invoice'),
317 'col_span' => 'sm:col-span-6'
318 ],
319 'easy_invoice_quote_accepted_message' => [
320 'label' => __('Accepted Quote Message', 'easy-invoice'),
321 'type' => 'wp_editor',
322 'default' => __('You\'ve confirmed the Quote.<br>We\'ll get in touch with you shortly.', 'easy-invoice'),
323 'description' => __('If the client accepts the Quote, display this message.', 'easy-invoice'),
324 'col_span' => 'sm:col-span-6'
325 ],
326 'easy_invoice_quote_decline_reason_required' => [
327 'label' => __('Decline Reason Required', 'easy-invoice'),
328 'type' => 'checkbox',
329 'default' => 'no',
330 'description' => __('Make the \'Reason for declining\' field mandatory when rejecting.', 'easy-invoice'),
331 'col_span' => 'sm:col-span-6'
332 ],
333 'easy_invoice_quote_declined_message' => [
334 'label' => __('Declined Quote Message', 'easy-invoice'),
335 'type' => 'wp_editor',
336 'default' => '',
337 'description' => __('Message to display if client declines the Quote', 'easy-invoice'),
338 'col_span' => 'sm:col-span-6'
339 ],
340 ],
341 ],
342 'currency' => [
343 'title' => __('Currency Settings', 'easy-invoice'),
344 'description' => __('Configure currency display preferences', 'easy-invoice'),
345 'icon' => 'fas fa-dollar-sign',
346 'fields' => [
347 'easy_invoice_currency_code' => [
348 'label' => __('Currency & Symbol', 'easy-invoice'),
349 'type' => 'select',
350 'default' => 'USD',
351 'options' => CurrencyHelper::getCurrencyOptions(),
352 'col_span' => 'sm:col-span-6',
353 'description' => __('Select your preferred currency. The symbol will be automatically set.', 'easy-invoice')
354 ],
355 'easy_invoice_currency_position' => [
356 'label' => __('Symbol Position', 'easy-invoice'),
357 'type' => 'select',
358 'default' => 'left',
359 'options' => [
360 'left' => __('Left', 'easy-invoice'),
361 'right' => __('Right', 'easy-invoice'),
362 'left_space' => __('Left with space', 'easy-invoice'),
363 'right_space' => __('Right with space', 'easy-invoice'),
364 ],
365 'col_span' => 'sm:col-span-3'
366 ],
367 'easy_invoice_currency_symbol_type' => [
368 'label' => __('Currency Symbol Type', 'easy-invoice'),
369 'type' => 'select',
370 'default' => 'symbol',
371 'options' => [
372 'code' => __('Currency Code', 'easy-invoice'),
373 'symbol' => __('Currency Symbol', 'easy-invoice'),
374 ],
375 'description' => __('Choose whether to display currency code (USD) or currency symbol ($)', 'easy-invoice'),
376 'col_span' => 'sm:col-span-3'
377 ],
378 'easy_invoice_thousands_separator' => ['label' => __('Thousands Separator', 'easy-invoice'), 'type' => 'text', 'default' => ',', 'col_span' => 'sm:col-span-3' ],
379 'easy_invoice_decimal_separator' => ['label' => __('Decimal Separator', 'easy-invoice'), 'type' => 'text', 'default' => '.', 'col_span' => 'sm:col-span-3' ],
380 'easy_invoice_decimal_precision' => ['label' => __('Decimal Places', 'easy-invoice'), 'type' => 'number', 'default' => '2', 'min' => 0, 'max' => 4, 'col_span' => 'sm:col-span-3' ],
381 ]
382 ],
383 'tax' => [
384 'title' => __('Tax Settings', 'easy-invoice'),
385 'description' => __('Configure tax rates and preferences', 'easy-invoice'),
386 'icon' => 'fas fa-percent',
387 'fields' => [
388 'easy_invoice_tax_enabled' => ['label' => __('Enable Tax', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6' ],
389 'easy_invoice_tax_entry_method' => [
390 'label' => __('How do you enter tax?', 'easy-invoice'),
391 'type' => 'select',
392 'default' => 'exclusive',
393 'options' => [
394 'inclusive' => __('I will enter price inclusive of tax', 'easy-invoice'),
395 'exclusive' => __('I will enter price exclusive of tax', 'easy-invoice'),
396 ],
397 'description' => __('Choose how you want to enter prices - with or without tax included', 'easy-invoice'),
398 'col_span' => 'sm:col-span-6'
399 ],
400 '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' ],
401 'easy_invoice_tax_name' => ['label' => __('Tax Name', 'easy-invoice'), 'type' => 'text', 'default' => __('Tax', 'easy-invoice'), 'col_span' => 'sm:col-span-3' ],
402 ]
403 ],
404 'payment' => [
405 'title' => __('Payment Methods', 'easy-invoice'),
406 'description' => __('Configure and order available payment methods.', 'easy-invoice'),
407 'icon' => 'fas fa-credit-card',
408 'is_special_section' => true,
409 'gateways' => $this->getGatewaySettingsConfigs(),
410 ],
411 'email' => [
412 'title' => __('Email Settings', 'easy-invoice'),
413 'description' => __('Configure email sending options and templates', 'easy-invoice'),
414 'icon' => 'fas fa-envelope',
415 'subsections' => [
416 'general' => [
417 'title' => __('General Email Settings', 'easy-invoice'),
418 'description' => __('Configure general email sending options', 'easy-invoice'),
419 'fields' => [
420 'easy_invoice_email_from_name' => ['label' => __('From Name', 'easy-invoice'), 'type' => 'text', 'default' => get_bloginfo('name') ?: 'Easy Invoice', 'col_span' => 'sm:col-span-3'],
421 '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'],
422 'easy_invoice_email_reply_to' => ['label' => __('Reply-To Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-3'],
423 'easy_invoice_email_reply_to_name' => ['label' => __('Reply-To Name', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3'],
424 'easy_invoice_enable_email_styling' => ['label' => __('Enable HTML Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
425 'easy_invoice_bcc_admin' => ['label' => __('BCC Admin on All Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6'],
426 'easy_invoice_admin_email' => ['label' => __('Admin Email for BCC', 'easy-invoice'), 'type' => 'email', 'default' => get_option('admin_email', ''), 'col_span' => 'sm:col-span-6'],
427 'easy_invoice_email_logo' => ['label' => __('Email Logo URL', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6'],
428 'easy_invoice_email_footer_text' => ['label' => __('Email Footer Text', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6'],
429 'easy_invoice_test_email' => [
430 'label' => __('Test Email', 'easy-invoice'),
431 'type' => 'test_email',
432 'default' => '',
433 'col_span' => 'sm:col-span-6',
434 'description' => __('Send a test email to verify your email configuration is working correctly.', 'easy-invoice')
435 ],
436 ]
437 ],
438 'invoice_available' => [
439 'title' => __('Invoice Available Email', 'easy-invoice'),
440 'description' => __('Configure email sent when an invoice is available for the client', 'easy-invoice'),
441 'fields' => [
442 'easy_invoice_invoice_email_enabled' => ['label' => __('Enable Invoice Available Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
443 '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'],
444 'easy_invoice_invoice_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('<h2>📄 Your Invoice is Ready</h2>
445
446 <p>Dear {{client_name}},</p>
447
448 <div class="highlight-box">
449 <p><strong>Invoice #{{invoice_number}}</strong><br>
450 <span class="amount-highlight">{{total_amount}}</span><br>
451 Due Date: <strong>{{due_date}}</strong></p>
452 </div>
453
454 <p>Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment.</p>
455
456 <div class="info-box">
457 <p><strong>📋 Payment Details:</strong><br>
458 • Invoice Number: {{invoice_number}}<br>
459 • Total Amount: {{total_amount}}<br>
460 • Due Date: {{due_date}}<br>
461 • Payment Terms: {{payment_terms}}</p>
462 </div>
463
464 <div class="warning-box">
465 <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
466 </div>
467
468 <p>If you have any questions about this invoice, please don\'t hesitate to contact us.</p>
469
470 <div class="divider"></div>
471
472 <p>Thank you for your business!</p>
473
474 <p>Best regards,<br>
475 <strong>{{company_name}}</strong><br>
476 {{company_email}}</p>', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
477 'easy_invoice_invoice_test_template' => [
478 'label' => __('Test Invoice Email', 'easy-invoice'),
479 'type' => 'test_template_email',
480 'default' => '',
481 'col_span' => 'sm:col-span-6',
482 'description' => __('Send a test invoice email to verify the template and configuration.', 'easy-invoice'),
483 'template_type' => 'invoice_available'
484 ],
485 ]
486 ],
487 'quote_available' => [
488 'title' => __('Quote Available Email', 'easy-invoice'),
489 'description' => __('Configure email sent when a quote is available for the client', 'easy-invoice'),
490 'fields' => [
491 'easy_invoice_quote_email_enabled' => ['label' => __('Enable Quote Available Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
492 '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'],
493 'easy_invoice_quote_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('<h2>📋 Your Quote is Ready</h2>
494
495 <p>Dear {{client_name}},</p>
496
497 <div class="highlight-box">
498 <p><strong>Quote #{{quote_number}}</strong><br>
499 <span class="amount-highlight">{{total_amount}}</span><br>
500 Valid Until: <strong>{{expiry_date}}</strong></p>
501 </div>
502
503 <p>We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment or visit the link below.</p>
504
505 <div class="info-box">
506 <p><strong>📋 Quote Summary:</strong><br>
507 • Quote Number: {{quote_number}}<br>
508 • Total Amount: {{total_amount}}<br>
509 • Valid Until: {{expiry_date}}<br>
510 • Terms: {{payment_terms}}</p>
511 </div>
512
513 <div class="highlight-box">
514 <p><strong>🔗 View Quote Online:</strong><br>
515 <a href="{{quote_url}}" style="color: #3b82f6; text-decoration: underline;">{{quote_url}}</a></p>
516 </div>
517
518 <div class="warning-box">
519 <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
520 </div>
521
522 <p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
523
524 <div class="divider"></div>
525
526 <p>We look forward to working with you!</p>
527
528 <p>Best regards,<br>
529 <strong>{{company_name}}</strong><br>
530 {{company_email}}</p>', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
531 'easy_invoice_quote_test_template' => [
532 'label' => __('Test Quote Email', 'easy-invoice'),
533 'type' => 'test_template_email',
534 'default' => '',
535 'col_span' => 'sm:col-span-6',
536 'description' => __('Send a test quote email to verify the template and configuration.', 'easy-invoice'),
537 'template_type' => 'quote_available'
538 ],
539 ]
540 ],
541 'payment_received' => [
542 'title' => __('Payment Received Email', 'easy-invoice'),
543 'description' => __('Configure email sent when a payment is received', 'easy-invoice'),
544 'fields' => [
545 'easy_invoice_payment_email_enabled' => ['label' => __('Enable Payment Received Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
546 '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'],
547 'easy_invoice_payment_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('<h2>�
548 Payment Received - Thank You!</h2>
549
550 <p>Dear {{client_name}},</p>
551
552 <div class="success-box">
553 <p><strong>Payment Confirmation</strong><br>
554 Invoice #{{invoice_number}}<br>
555 <span class="amount-highlight">{{payment_amount}}</span><br>
556 Payment Date: <strong>{{payment_date}}</strong><br>
557 Payment Method: <strong>{{payment_method}}</strong></p>
558 </div>
559
560 <p>We have successfully received your payment. Thank you for your prompt payment!</p>
561
562 <div class="info-box">
563 <p><strong>📊 Payment Details:</strong><br>
564 Invoice Number: {{invoice_number}}<br>
565 Amount Paid: {{payment_amount}}<br>
566 Payment Date: {{payment_date}}<br>
567 Payment Method: {{payment_method}}<br>
568 Transaction ID: {{transaction_id}}</p>
569 </div>
570
571 <div class="highlight-box">
572 <p><strong>🎉 Status: PAID</strong><br>
573 Your payment has been processed and your account is now up to date. We appreciate your business!</p>
574 </div>
575
576 <p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
577
578 <div class="divider"></div>
579
580 <p>Thank you for choosing our services!</p>
581
582 <p>Best regards,<br>
583 <strong>{{company_name}}</strong><br>
584 {{company_email}}</p>', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
585 'easy_invoice_payment_test_template' => [
586 'label' => __('Test Payment Email', 'easy-invoice'),
587 'type' => 'test_template_email',
588 'default' => '',
589 'col_span' => 'sm:col-span-6',
590 'description' => __('Send a test payment email to verify the template and configuration.', 'easy-invoice'),
591 'template_type' => 'payment_received'
592 ],
593 ]
594 ],
595 ]
596 ],
597 'text_settings' => [
598 'title' => __('Text Settings', 'easy-invoice'),
599 'description' => __('Customize text labels for invoices and quotes. Perfect for multi-language support.', 'easy-invoice'),
600 'icon' => '<svg class="text-indigo-600 text-xl h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
601 <path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
602 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
603 <path d="M8 11H16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
604 <path d="M8 15H12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
605 <path d="M8 19H14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
606 </svg>',
607 'fields' => [
608 // Invoice Text Settings
609 'easy_invoice_text_invoice' => ['label' => __('Invoice', 'easy-invoice'), 'type' => 'text', 'default' => __('Invoice', 'easy-invoice'), 'col_span' => '', 'description' => __('Label for single invoice', 'easy-invoice')],
610 'easy_invoice_text_invoices' => ['label' => __('Invoices', 'easy-invoice'), 'type' => 'text', 'default' => __('Invoices', 'easy-invoice'), 'col_span' => '', 'description' => __('Label for multiple invoices', 'easy-invoice')],
611 'easy_invoice_text_to' => ['label' => __('To', 'easy-invoice'), 'type' => 'text', 'default' => __('To', 'easy-invoice'), 'col_span' => '', 'description' => __('To label on invoice/quote', 'easy-invoice')],
612 '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')],
613 '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')],
614 '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')],
615 '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')],
616 'easy_invoice_text_qty' => ['label' => __('Qty', 'easy-invoice'), 'type' => 'text', 'default' => __('Qty', 'easy-invoice'), 'col_span' => '', 'description' => __('Quantity label', 'easy-invoice')],
617 'easy_invoice_text_service' => ['label' => __('Service', 'easy-invoice'), 'type' => 'text', 'default' => __('Service', 'easy-invoice'), 'col_span' => '', 'description' => __('Service label', 'easy-invoice')],
618 'easy_invoice_text_rate_price' => ['label' => __('Rate', 'easy-invoice'), 'type' => 'text', 'default' => __('Rate', 'easy-invoice'), 'col_span' => '', 'description' => __('Rate or price label', 'easy-invoice')],
619 'easy_invoice_text_adjust' => ['label' => __('Adjust', 'easy-invoice'), 'type' => 'text', 'default' => __('Adjust', 'easy-invoice'), 'col_span' => '', 'description' => __('Adjust label', 'easy-invoice')],
620 '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')],
621 'easy_invoice_text_total' => ['label' => __('Total', 'easy-invoice'), 'type' => 'text', 'default' => __('Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Total label', 'easy-invoice')],
622 'easy_invoice_text_tax' => ['label' => __('Tax', 'easy-invoice'), 'type' => 'text', 'default' => __('Tax', 'easy-invoice'), 'col_span' => '', 'description' => __('Tax label', 'easy-invoice')],
623 'easy_invoice_text_discount' => ['label' => __('Discount', 'easy-invoice'), 'type' => 'text', 'default' => __('Discount', 'easy-invoice'), 'col_span' => '', 'description' => __('Discount label', 'easy-invoice')],
624 'easy_invoice_text_print' => ['label' => __('Print', 'easy-invoice'), 'type' => 'text', 'default' => __('Print', 'easy-invoice'), 'col_span' => '', 'description' => __('Print button text', 'easy-invoice')],
625 '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')],
626 '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')],
627 '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')],
628
629 // Quote Text Settings
630 'easy_invoice_text_quote' => ['label' => __('Quote', 'easy-invoice'), 'type' => 'text', 'default' => __('Quote', 'easy-invoice'), 'col_span' => '', 'description' => __('Label for single quote', 'easy-invoice')],
631 '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')],
632 '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')],
633 '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')],
634 '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')],
635 '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')],
636 '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')],
637 ]
638 ],
639 'advanced' => [
640 'title' => __('Advanced Settings', 'easy-invoice'),
641 'description' => __('Configure system preferences', 'easy-invoice'),
642 'icon' => 'fas fa-cog',
643 'fields' => [
644 'easy_invoice_date_format' => [
645 'label' => __('Date Format', 'easy-invoice'),
646 'type' => 'select',
647 'default' => 'us',
648 'options' => [
649 'us' => __('MM/DD/YYYY (US) - 01/15/2024', 'easy-invoice'),
650 'uk' => __('DD/MM/YYYY (UK) - 15/01/2024', 'easy-invoice'),
651 'iso' => __('YYYY-MM-DD (ISO) - 2024-01-15', 'easy-invoice'),
652 ],
653 'col_span' => 'sm:col-span-3'
654 ],
655
656 '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' ],
657 'easy_invoice_payment_reminder_days' => ['label' => __('Payment Reminder Days', 'easy-invoice'), 'type' => 'number', 'default' => 3, 'col_span' => 'sm:col-span-3'],
658 ]
659 ],
660 ];
661
662 // Cache and filter the config
663 $this->config_cache = apply_filters('easy_invoice_settings_fields_config', $config);
664
665 return $this->config_cache;
666 }
667
668 /**
669 * Get all gateway settings configurations
670 *
671 * @return array Gateway settings configs
672 */
673 private function getGatewaySettingsConfigs(): array {
674 $gateway_configs = [];
675
676 try {
677 // Get gateway manager from the main plugin instance
678 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
679 $gateways = $gateway_manager->getGateways();
680
681 // Collect settings from each gateway
682 foreach ($gateways as $gateway_id => $gateway) {
683 try {
684 $gateway_configs[$gateway_id] = $gateway->getSettingsConfig();
685 } catch (\Exception $e) {
686 // Log the error but continue with other gateways
687 $gateway_configs[$gateway_id] = [];
688 }
689 }
690 } catch (\Exception $e) {
691 }
692
693 return $gateway_configs;
694 }
695
696 /**
697 * Prepare settings for display with proper filtering
698 *
699 * @param array $settings The settings array
700 * @return array The filtered settings
701 */
702 public function prepareSettingsForDisplay($settings) {
703 return apply_filters('easy_invoice_settings_for_display', $settings);
704 }
705
706 /**
707 * Display method implementation
708 *
709 * @param array $args Display arguments
710 * @return void
711 */
712 public function display(array $args = []) {
713 $page = isset($args['page']) ? $args['page'] : '';
714 $subsection = isset($args['subsection']) ? $args['subsection'] : '';
715
716 // Clear cache to ensure new fields are recognized
717 $this->clearCache();
718
719 switch ($page) {
720 case PagesSlugs::SETTINGS:
721 if (!empty($subsection)) {
722 // Handle email settings subsections
723 $this->displayEmailSettingsPage($subsection);
724 } else {
725 $this->displaySettingsPage();
726 }
727 break;
728
729 default:
730 $this->displaySettingsPage();
731 break;
732 }
733 }
734
735 /**
736 * Display email settings page for specific subsection
737 *
738 * @param string $subsection The subsection to display
739 * @return void
740 */
741 protected function displayEmailSettingsPage($subsection) {
742 // Get settings configuration
743 $settings_config = $this->get_settings_fields_config();
744 $settings = $this->getSettings();
745
746 // Filter to show only email settings
747 $email_config = $settings_config['email'] ?? [];
748
749 // Map subsection to the appropriate subsection in email config
750 $subsection_mapping = [
751 PagesSlugs::EMAIL_SETTINGS_GENERAL => 'general',
752 PagesSlugs::EMAIL_SETTINGS_INVOICE => 'invoice_available',
753 PagesSlugs::EMAIL_SETTINGS_QUOTE => 'quote_available',
754 PagesSlugs::EMAIL_SETTINGS_PAYMENT => 'payment_received',
755 PagesSlugs::EMAIL_SETTINGS_PAYMENT_REMINDER => 'payment_reminder',
756 ];
757
758 $target_subsection = $subsection_mapping[$subsection] ?? 'general';
759
760 // Create a modified config with only the target subsection
761 $filtered_config = [
762 'email' => [
763 'title' => __('Email Settings', 'easy-invoice'),
764 'description' => __('Configure email sending options and templates', 'easy-invoice'),
765 'icon' => 'fas fa-envelope',
766 'subsections' => [
767 $target_subsection => $email_config['subsections'][$target_subsection] ?? []
768 ]
769 ]
770 ];
771
772 // Include the settings page template with filtered config
773 include EASY_INVOICE_PLUGIN_DIR . 'templates/settings-page.php';
774 }
775
776 /**
777 * Display settings page
778 *
779 * @return void
780 */
781 protected function displaySettingsPage() {
782 $settings = $this->getSettings();
783
784 // Allow developers to modify settings before display
785 $settings = apply_filters('easy_invoice_before_display_settings', $settings);
786
787 // Ensure payment methods are set (this is critical for gateway checkboxes)
788 if (!isset($settings['easy_invoice_payment_methods']) || !is_array($settings['easy_invoice_payment_methods'])) {
789 $settings['easy_invoice_payment_methods'] = get_option('easy_invoice_payment_methods', []);
790 }
791
792 // Ensure gateway order is set
793 if (!isset($settings['easy_invoice_payment_gateway_order']) || !is_array($settings['easy_invoice_payment_gateway_order'])) {
794 $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []);
795 }
796
797 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
798 $all_gateways_unsorted = $gateway_manager->getGateways();
799 $gateway_order_option = $settings['easy_invoice_payment_gateway_order'];
800 $payment_methods_enabled = $settings['easy_invoice_payment_methods'];
801
802 $all_gateways_sorted = [];
803 if (!empty($gateway_order_option)) {
804 foreach ($gateway_order_option as $gateway_id) {
805 if (isset($all_gateways_unsorted[$gateway_id])) {
806 $all_gateways_sorted[$gateway_id] = $all_gateways_unsorted[$gateway_id];
807 unset($all_gateways_unsorted[$gateway_id]);
808 }
809 }
810 }
811 $all_gateways_sorted = array_merge($all_gateways_sorted, $all_gateways_unsorted);
812
813 $settings_config = $this->get_settings_fields_config();
814
815 // Prepare template variables
816 $template_args = apply_filters('easy_invoice_settings_template_args', [
817 'settings' => $settings,
818 'settings_config' => $settings_config,
819 'all_gateways_sorted' => $all_gateways_sorted,
820 'payment_methods_enabled' => $payment_methods_enabled
821 ]);
822
823 // Display the template with template args
824 $this->displayTemplate(
825 apply_filters('easy_invoice_settings_template_path', EASY_INVOICE_PLUGIN_DIR . 'templates/settings-page.php'),
826 $template_args
827 );
828
829 // Action for plugins to add their own content after the settings page
830 do_action('easy_invoice_after_settings_page');
831 }
832
833 /**
834 * Save payment settings
835 *
836 * @param array $posted_settings Posted settings
837 * @param array $response Response array
838 * @return void
839 */
840 private function savePaymentSettings($posted_settings, &$response) {
841 // Handle payment methods
842 if (isset($posted_settings['easy_invoice_payment_methods']) && is_array($posted_settings['easy_invoice_payment_methods'])) {
843 $sanitized_methods = array_map('sanitize_key', $posted_settings['easy_invoice_payment_methods']);
844 update_option('easy_invoice_payment_methods', $sanitized_methods);
845 $response['saved_options']['easy_invoice_payment_methods'] = $sanitized_methods;
846 } else {
847 update_option('easy_invoice_payment_methods', []);
848 $response['saved_options']['easy_invoice_payment_methods'] = [];
849 }
850
851 // Handle gateway order
852 if (isset($posted_settings['easy_invoice_gateway_order']) && is_array($posted_settings['easy_invoice_gateway_order'])) {
853 $gateway_order = array_map('sanitize_key', $posted_settings['easy_invoice_gateway_order']);
854 update_option('easy_invoice_payment_gateway_order', $gateway_order);
855 $response['saved_options']['easy_invoice_gateway_order'] = $gateway_order;
856 } else {
857 update_option('easy_invoice_payment_gateway_order', []);
858 $response['saved_options']['easy_invoice_gateway_order'] = [];
859 }
860
861 // Handle gateway display names
862 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
863 $all_gateways = $gateway_manager->getGateways();
864
865 foreach ($all_gateways as $gateway_id => $gateway) {
866 $display_name_key = 'easy_invoice_gateway_display_name_' . $gateway_id;
867 if (isset($posted_settings[$display_name_key])) {
868 $display_name = wp_strip_all_tags(wp_unslash($posted_settings[$display_name_key]));
869 update_option($display_name_key, $display_name);
870 $response['saved_options'][$display_name_key] = $display_name;
871 }
872 }
873
874 // Handle gateway-specific settings
875 try {
876 $gateway_configs = $this->getGatewaySettingsConfigs();
877 foreach ($gateway_configs as $gateway_id => $gateway_config) {
878 if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) {
879 foreach ($gateway_config['fields'] as $option_key => $field_config) {
880 if (isset($posted_settings[$option_key])) {
881 $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response);
882 } elseif ($field_config['type'] === 'checkbox') {
883 // Checkboxes not in POST are considered unchecked
884 update_option($option_key, 'no');
885 $response['saved_options'][$option_key] = 'no';
886 }
887 }
888 }
889 }
890 } catch (\Exception $e) {
891 throw $e; // Re-throw to be caught by the main try-catch
892 }
893 }
894
895 /**
896 * Save settings
897 *
898 * @return void
899 */
900 public function saveSettings() {
901 // Verify nonce
902 if (!wp_verify_nonce($_POST['easy_invoice_settings_nonce'], 'easy_invoice_settings')) {
903 wp_die(__('Security check failed', 'easy-invoice'));
904 }
905
906
907 // Check permissions
908 if (!current_user_can('manage_options')) {
909 wp_die(__('You do not have permission to perform this action', 'easy-invoice'));
910 }
911
912 $posted_settings = $_POST['settings'] ?? [];
913
914 $settings_config = $this->get_settings_fields_config();
915 $response = [
916 'success' => true,
917 'message' => __('Settings saved successfully', 'easy-invoice'),
918 'saved_options' => []
919 ];
920
921 try {
922 // Process each section
923 foreach ($settings_config as $section_id => $section_data) {
924 if (isset($section_data['fields']) && is_array($section_data['fields'])) {
925 // Handle regular fields
926 foreach ($section_data['fields'] as $option_key => $field_config) {
927 if (isset($posted_settings[$option_key])) {
928 $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response);
929 } elseif ($field_config['type'] === 'checkbox') {
930 // Checkboxes not in POST are considered unchecked
931 update_option($option_key, 'no');
932 $response['saved_options'][$option_key] = 'no';
933 }
934 }
935 } else if (isset($section_data['subsections']) && is_array($section_data['subsections'])) {
936 // Handle sections with subsections (like invoice, quote, email, etc.)
937 foreach ($section_data['subsections'] as $subsection_id => $subsection_data) {
938 if (isset($subsection_data['fields']) && is_array($subsection_data['fields'])) {
939 foreach ($subsection_data['fields'] as $option_key => $field_config) {
940 if (isset($posted_settings[$option_key])) {
941 $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response);
942 } elseif ($field_config['type'] === 'checkbox') {
943 // Checkboxes not in POST are considered unchecked
944 update_option($option_key, 'no');
945 $response['saved_options'][$option_key] = 'no';
946 }
947 }
948 }
949 }
950 }
951 }
952
953 // Handle special sections like payment methods
954 if (isset($settings_config['payment']) && isset($settings_config['payment']['is_special_section'])) {
955 $this->savePaymentSettings($posted_settings, $response);
956 }
957
958 // Clear any caches
959 wp_cache_flush();
960
961 // Clear settings cache
962 $this->settings_cache = null;
963
964 // Log the save action
965 $this->log('Settings saved successfully by user: ' . get_current_user_id());
966
967 } catch (\Exception $e) {
968 $response['success'] = false;
969 $response['message'] = __('Error saving settings: ', 'easy-invoice') . $e->getMessage();
970 $this->log('Error saving settings: ' . $e->getMessage());
971
972 }
973
974 // Clear settings cache to ensure new fields are recognized
975 $this->clearCache();
976
977 // Send JSON response
978 wp_send_json($response);
979 }
980
981 /**
982 * Sanitize and save a single setting
983 *
984 * @param string $option_key The option key
985 * @param array $field_config The field configuration
986 * @param mixed $value The value to sanitize and save
987 * @param array &$response The response array to update
988 * @return void
989 */
990 private function sanitizeAndSaveSetting($option_key, $field_config, $value, &$response) {
991 if (in_array($option_key, [
992 'easy_invoice_quote_accept_text',
993 'easy_invoice_quote_accepted_message',
994 'easy_invoice_quote_declined_message'
995 ])) {
996 }
997
998 // Unslash the value to prevent double-escaping
999 $value = wp_unslash($value);
1000
1001 // Allow pre-sanitization filters
1002 $value = apply_filters('easy_invoice_pre_sanitize_option', $value, $option_key, $field_config);
1003
1004 // Sanitize based on field type
1005 switch ($field_config['type']) {
1006 case 'email':
1007 $value = sanitize_email($value);
1008 break;
1009 case 'url':
1010 $value = esc_url_raw($value);
1011 break;
1012 case 'textarea':
1013 // Use wp_kses_post for textarea to allow safe HTML while preventing double-escaping
1014 $value = wp_kses_post($value);
1015 break;
1016 case 'wp_editor':
1017 // For wp_editor content, use WordPress's built-in sanitization
1018 if (is_string($value)) {
1019 // Use wp_kses_post which expects unslashed data
1020 $value = wp_kses_post($value);
1021 }
1022 break;
1023 case 'number':
1024 if (isset($field_config['step']) && strpos((string)$field_config['step'], '.') !== false) {
1025 $value = floatval(wp_strip_all_tags(easy_invoice_str_replace(',', '.', $value)));
1026 } elseif (isset($field_config['min']) && intval($field_config['min']) < 0) {
1027 $value = intval($value);
1028 } else {
1029 $value = absint($value);
1030 }
1031 break;
1032 case 'checkbox':
1033 // For checkboxes, we expect 'yes' or 'no' values
1034 $value = ($value === 'yes' || $value === '1' || $value === true) ? 'yes' : 'no';
1035 break;
1036 case 'select':
1037 $value = sanitize_key($value);
1038 // Ensure currency codes are always uppercase
1039 if ($option_key === 'easy_invoice_currency_code') {
1040 $value = strtoupper($value);
1041 }
1042 break;
1043 case 'multiselect':
1044 // For multiselect, ensure it's an array and sanitize each value
1045 if (!is_array($value)) {
1046 $value = [];
1047 } else {
1048 $value = array_map('sanitize_key', $value);
1049 }
1050 break;
1051 default:
1052 // For regular text fields, use wp_strip_all_tags to prevent double-escaping
1053 $value = wp_strip_all_tags($value);
1054 break;
1055 }
1056
1057 // Allow post-sanitization value modification
1058 $value = apply_filters("easy_invoice_sanitize_option_{$option_key}", $value, $field_config);
1059 $value = apply_filters('easy_invoice_sanitize_option', $value, $option_key, $field_config);
1060
1061 // Save the setting normally
1062 update_option($option_key, $value);
1063 $response['saved_options'][$option_key] = $value;
1064 }
1065
1066 /**
1067 * Get all settings for the plugin
1068 *
1069 * @param bool $use_cache Whether to use cached settings
1070 * @return array The settings array
1071 */
1072 public function getSettings($use_cache = true): array {
1073 // Return cached settings if available
1074 if ($use_cache && is_array($this->settings_cache)) {
1075 return $this->settings_cache;
1076 }
1077
1078 $settings = [];
1079 $config = $this->get_settings_fields_config();
1080
1081 foreach ($config as $section_id => $section_data) {
1082 if ($section_id === 'payment' && isset($section_data['gateways'])) {
1083 // Handle gateway fields specifically
1084 foreach ($section_data['gateways'] as $gateway_id => $gateway_config) {
1085 if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) {
1086 foreach ($gateway_config['fields'] as $option_key => $field_data) {
1087 $default_value = $field_data['default'] ?? '';
1088 $settings[$option_key] = get_option($option_key, $default_value);
1089 }
1090 }
1091 }
1092 }
1093 else if (isset($section_data['subsections']) && is_array($section_data['subsections'])) {
1094 // Handle sections with subsections (like email, invoice, quote)
1095 foreach ($section_data['subsections'] as $subsection_id => $subsection_data) {
1096 if (isset($subsection_data['fields']) && is_array($subsection_data['fields'])) {
1097 foreach ($subsection_data['fields'] as $option_key => $field_data) {
1098 $default_value = $field_data['default'] ?? '';
1099
1100 // Handle special defaults
1101 if ($option_key === 'easy_invoice_email_from_name' && $default_value === get_bloginfo('name')) {
1102 $default_value = get_bloginfo('name');
1103 }
1104 if ($option_key === 'easy_invoice_email_from_address' && $default_value === get_bloginfo('admin_email')) {
1105 $default_value = get_bloginfo('admin_email');
1106 }
1107
1108 // Check if field has a value_callback function
1109 if (isset($field_data['value_callback']) && is_callable($field_data['value_callback'])) {
1110 $settings[$option_key] = $field_data['value_callback']();
1111 } else {
1112 // Get option value or default
1113 $settings[$option_key] = get_option($option_key, $default_value);
1114 }
1115 }
1116 }
1117 }
1118 }
1119 else if (isset($section_data['fields']) && is_array($section_data['fields'])) {
1120 foreach ($section_data['fields'] as $option_key => $field_data) {
1121 $default_value = $field_data['default'] ?? '';
1122
1123 // Handle special defaults
1124 if ($option_key === 'easy_invoice_email_from_name' && $default_value === get_bloginfo('name')) {
1125 $default_value = get_bloginfo('name');
1126 }
1127 if ($option_key === 'easy_invoice_email_from_address' && $default_value === get_bloginfo('admin_email')) {
1128 $default_value = get_bloginfo('admin_email');
1129 }
1130
1131 // Check if field has a value_callback function
1132 if (isset($field_data['value_callback']) && is_callable($field_data['value_callback'])) {
1133 $settings[$option_key] = $field_data['value_callback']();
1134 } else {
1135 // Get option value or default
1136 $settings[$option_key] = get_option($option_key, $default_value);
1137 }
1138 }
1139 }
1140 }
1141
1142 // Add special array settings
1143 $settings['easy_invoice_payment_methods'] = get_option('easy_invoice_payment_methods', []);
1144 $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []);
1145
1146 // Cache settings
1147 $this->settings_cache = $settings;
1148
1149 // Allow third party to add/modify settings
1150 return apply_filters('easy_invoice_settings', $settings);
1151 }
1152
1153 /**
1154 * Log debug information
1155 *
1156 * @param string $message The message to log
1157 * @return void
1158 */
1159 protected function log(string $message): void {
1160
1161 }
1162
1163 /**
1164 * Initialize default settings
1165 */
1166 public function initializeSettings() {
1167 // One-time migration (2.1.22): strip the leftover
1168 // "<strong>Shortcode:</strong> <code>[easy_(invoice|quote)_url …]</code>"
1169 // reference line from saved email bodies. Originally intended as admin-side
1170 // documentation, but it lived inside the email template and got emailed to
1171 // clients verbatim. Guarded by a flag option so each install runs at most once.
1172 //
1173 // The regex tolerates the optional surrounding <p>...</p> because WP's
1174 // wp_kses_post may normalise the wrapping when the option is saved.
1175 if (!get_option('easy_invoice_email_shortcode_hint_removed_v2')) {
1176 foreach (['easy_invoice_invoice_email_body', 'easy_invoice_quote_email_body'] as $opt_key) {
1177 $body = get_option($opt_key, '');
1178 if (!is_string($body) || $body === '') {
1179 continue;
1180 }
1181 $cleaned = preg_replace(
1182 '#\s*(?:<p>\s*)?<strong>\s*Shortcode\s*:\s*</strong>\s*<code>\s*\[easy_(?:invoice|quote)_url[^\]]*\]\s*</code>(?:\s*</p>)?\s*#i',
1183 '',
1184 $body
1185 );
1186 if ($cleaned !== null && $cleaned !== $body) {
1187 update_option($opt_key, $cleaned);
1188 }
1189 }
1190 update_option('easy_invoice_email_shortcode_hint_removed_v2', 1);
1191 }
1192
1193 // Initialize invoice number settings if not already set
1194 if (!get_option('easy_invoice_invoice_prefix')) {
1195 update_option('easy_invoice_invoice_prefix', 'INV-');
1196 }
1197
1198 if (!get_option('easy_invoice_next_invoice_number')) {
1199 update_option('easy_invoice_next_invoice_number', 1);
1200 }
1201
1202 // Initialize other default settings
1203 if (!get_option('easy_invoice_invoice_due_days')) {
1204 update_option('easy_invoice_invoice_due_days', 30);
1205 }
1206
1207 if (!get_option('easy_invoice_currency_code')) {
1208 update_option('easy_invoice_currency_code', 'USD');
1209 }
1210
1211 if (!get_option('easy_invoice_currency_symbol')) {
1212 update_option('easy_invoice_currency_symbol', '$');
1213 }
1214
1215 if (!get_option('easy_invoice_currency_position')) {
1216 update_option('easy_invoice_currency_position', 'left');
1217 }
1218
1219 if (!get_option('easy_invoice_currency_symbol_type')) {
1220 update_option('easy_invoice_currency_symbol_type', 'symbol');
1221 }
1222
1223 if (!get_option('easy_invoice_decimal_separator')) {
1224 update_option('easy_invoice_decimal_separator', '.');
1225 }
1226
1227 if (!get_option('easy_invoice_thousands_separator')) {
1228 update_option('easy_invoice_thousands_separator', ',');
1229 }
1230
1231 if (!get_option('easy_invoice_decimal_precision')) {
1232 update_option('easy_invoice_decimal_precision', 2);
1233 }
1234
1235 // Initialize date format setting
1236 if (!get_option('easy_invoice_date_format')) {
1237 update_option('easy_invoice_date_format', 'us');
1238 }
1239
1240 // Fix legacy date format values
1241 $current_date_format = get_option('easy_invoice_date_format');
1242 if ($current_date_format === 'mdy' || $current_date_format === 'm/d/Y') {
1243 update_option('easy_invoice_date_format', 'us');
1244 } elseif ($current_date_format === 'd/m/Y') {
1245 update_option('easy_invoice_date_format', 'uk');
1246 } elseif ($current_date_format === 'Y-m-d') {
1247 update_option('easy_invoice_date_format', 'iso');
1248 }
1249
1250 // Initialize email settings
1251 if (!get_option('easy_invoice_email_from_name')) {
1252 update_option('easy_invoice_email_from_name', get_bloginfo('name'));
1253 }
1254
1255 if (!get_option('easy_invoice_email_from_address')) {
1256 update_option('easy_invoice_email_from_address', get_bloginfo('admin_email'));
1257 }
1258
1259 if (!get_option('easy_invoice_enable_email_styling')) {
1260 update_option('easy_invoice_enable_email_styling', 'yes');
1261 }
1262
1263 if (!get_option('easy_invoice_bcc_admin')) {
1264 update_option('easy_invoice_bcc_admin', 'no');
1265 }
1266
1267 if (!get_option('easy_invoice_admin_email')) {
1268 update_option('easy_invoice_admin_email', get_option('admin_email'));
1269 }
1270
1271 // Initialize Text Settings
1272 $text_settings = [
1273 'easy_invoice_text_invoice' => __('Invoice', 'easy-invoice'),
1274 'easy_invoice_text_invoices' => __('Invoices', 'easy-invoice'),
1275 'easy_invoice_text_from' => __('From', 'easy-invoice'),
1276 'easy_invoice_text_to' => __('To', 'easy-invoice'),
1277 'easy_invoice_text_invoice_number' => __('Invoice Number', 'easy-invoice'),
1278 'easy_invoice_text_order_number' => __('Order Number', 'easy-invoice'),
1279 'easy_invoice_text_invoice_date' => __('Invoice Date', 'easy-invoice'),
1280 'easy_invoice_text_due_date' => __('Due Date', 'easy-invoice'),
1281 'easy_invoice_text_total_due' => __('Total Due', 'easy-invoice'),
1282 'easy_invoice_text_qty' => __('Qty', 'easy-invoice'),
1283 'easy_invoice_text_service' => __('Service', 'easy-invoice'),
1284 'easy_invoice_text_rate_price' => __('Rate', 'easy-invoice'),
1285 'easy_invoice_text_adjust' => __('Adjust', 'easy-invoice'),
1286 'easy_invoice_text_sub_total' => __('Sub Total', 'easy-invoice'),
1287 'easy_invoice_text_total' => __('Total', 'easy-invoice'),
1288 'easy_invoice_text_tax' => __('Tax', 'easy-invoice'),
1289 'easy_invoice_text_discount' => __('Discount', 'easy-invoice'),
1290 'easy_invoice_text_page' => __('Page', 'easy-invoice'),
1291 'easy_invoice_text_print' => __('Print', 'easy-invoice'),
1292 'easy_invoice_text_download_pdf' => __('Download as PDF', 'easy-invoice'),
1293 'easy_invoice_text_send_email' => __('Send Email', 'easy-invoice'),
1294 'easy_invoice_text_pay_now' => __('Pay Now', 'easy-invoice'),
1295 'easy_invoice_text_proceed_payment' => __('Proceed to payment', 'easy-invoice'),
1296 'easy_invoice_text_payment_gateway' => __('Invoice Payment Gateway', 'easy-invoice'),
1297 'easy_invoice_text_quote' => __('Quote', 'easy-invoice'),
1298 'easy_invoice_text_quotes' => __('Quotes', 'easy-invoice'),
1299 'easy_invoice_text_quote_number' => __('Quote Number', 'easy-invoice'),
1300 'easy_invoice_text_accept_quote' => __('Accept Quote', 'easy-invoice'),
1301 'easy_invoice_text_decline_quote' => __('Decline Quote', 'easy-invoice'),
1302 'easy_invoice_text_decline_reason' => __('Reason for declining', 'easy-invoice'),
1303 'easy_invoice_text_quote_amount' => __('Quote Amount', 'easy-invoice'),
1304 'easy_invoice_text_valid_until' => __('Valid Until Date', 'easy-invoice'),
1305 'easy_invoice_text_quote_date' => __('Quote Date', 'easy-invoice'),
1306 'easy_invoice_text_available' => __('Available', 'easy-invoice'),
1307 'easy_invoice_text_draft' => __('Draft', 'easy-invoice'),
1308 'easy_invoice_text_overdue' => __('Overdue', 'easy-invoice'),
1309 'easy_invoice_text_paid' => __('Paid', 'easy-invoice'),
1310 'easy_invoice_text_unpaid' => __('Unpaid', 'easy-invoice'),
1311 'easy_invoice_text_cancelled' => __('Cancelled', 'easy-invoice'),
1312 ];
1313
1314 foreach ($text_settings as $option_key => $default_value) {
1315 if (!get_option($option_key)) {
1316 update_option($option_key, $default_value);
1317 }
1318 }
1319
1320 if (!get_option('easy_invoice_invoice_email_enabled')) {
1321 update_option('easy_invoice_invoice_email_enabled', 'yes');
1322 }
1323
1324 if (!get_option('easy_invoice_invoice_email_subject')) {
1325 update_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1326 }
1327
1328 if (!get_option('easy_invoice_invoice_email_body')) {
1329 update_option('easy_invoice_invoice_email_body', __('<h2>📄 Your Invoice is Ready</h2>
1330
1331 <p>Dear {{client_name}},</p>
1332
1333 <div class="highlight-box">
1334 <p><strong>Invoice #{{invoice_number}}</strong><br>
1335 <span class="amount-highlight">{{total_amount}}</span><br>
1336 Due Date: <strong>{{due_date}}</strong></p>
1337 </div>
1338
1339 <p>Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment or visit the link below.</p>
1340
1341 <div class="info-box">
1342 <p><strong>📋 Payment Details:</strong><br>
1343 • Invoice Number: {{invoice_number}}<br>
1344 • Total Amount: {{total_amount}}<br>
1345 • Due Date: {{due_date}}<br>
1346 • Payment Terms: {{payment_terms}}</p>
1347 </div>
1348
1349 <div class="highlight-box">
1350 <p><strong>🔗 View Invoice Online:</strong><br>
1351 <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1352 </div>
1353
1354 <div class="warning-box">
1355 <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1356 </div>
1357
1358 <p>If you have any questions about this invoice, please don\'t hesitate to contact us.</p>
1359
1360 <div class="divider"></div>
1361
1362 <p>Thank you for your business!</p>
1363
1364 <p>Best regards,<br>
1365 <strong>{{company_name}}</strong><br>
1366 {{company_email}}</p>', 'easy-invoice'));
1367 }
1368
1369 if (!get_option('easy_invoice_quote_email_enabled')) {
1370 update_option('easy_invoice_quote_email_enabled', 'yes');
1371 }
1372
1373 if (!get_option('easy_invoice_quote_email_subject')) {
1374 update_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'));
1375 }
1376
1377 if (!get_option('easy_invoice_quote_email_body')) {
1378 update_option('easy_invoice_quote_email_body', __('<h2>Your Quote is Ready</h2>
1379
1380 <p>Dear {{client_name}},</p>
1381
1382 <div class="highlight-box">
1383 <p><strong>Quote #{{quote_number}}</strong><br>
1384 Total Amount: <strong>{{total_amount}}</strong><br>
1385 Valid Until: <strong>{{expiry_date}}</strong></p>
1386 </div>
1387
1388 <p>We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment or visit the link below.</p>
1389
1390 <div class="info-box">
1391 <p><strong>Quote Summary:</strong><br>
1392 • Quote Number: {{quote_number}}<br>
1393 • Total Amount: {{total_amount}}<br>
1394 • Valid Until: {{expiry_date}}<br>
1395 • Terms: {{payment_terms}}</p>
1396 </div>
1397
1398 <div class="highlight-box">
1399 <p><strong>🔗 View Quote Online:</strong><br>
1400 <a href="{{quote_url}}" style="color: #3b82f6; text-decoration: underline;">{{quote_url}}</a></p>
1401 </div>
1402
1403 <p>This quote is valid until {{expiry_date}}. If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
1404
1405 <p>We look forward to working with you!</p>
1406
1407 <p>Best regards,<br>
1408 <strong>{{company_name}}</strong><br>
1409 {{company_email}}</p>', 'easy-invoice'));
1410 }
1411
1412 if (!get_option('easy_invoice_payment_email_enabled')) {
1413 update_option('easy_invoice_payment_email_enabled', 'yes');
1414 }
1415
1416 if (!get_option('easy_invoice_payment_email_subject')) {
1417 update_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'));
1418 }
1419
1420 if (!get_option('easy_invoice_payment_email_body')) {
1421 update_option('easy_invoice_payment_email_body', __('<h2>�
1422 Payment Received - Thank You!</h2>
1423
1424 <p>Dear {{client_name}},</p>
1425
1426 <div class="success-box">
1427 <p><strong>Payment Confirmation</strong><br>
1428 Invoice #{{invoice_number}}<br>
1429 <span class="amount-highlight">{{payment_amount}}</span><br>
1430 Payment Date: <strong>{{payment_date}}</strong><br>
1431 Payment Method: <strong>{{payment_method}}</strong></p>
1432 </div>
1433
1434 <p>We have successfully received your payment. Thank you for your prompt payment!</p>
1435
1436 <div class="info-box">
1437 <p><strong>📊 Payment Details:</strong><br>
1438 Invoice Number: {{invoice_number}}<br>
1439 Amount Paid: {{payment_amount}}<br>
1440 Payment Date: {{payment_date}}<br>
1441 Payment Method: {{payment_method}}<br>
1442 Transaction ID: {{transaction_id}}</p>
1443 </div>
1444
1445 <div class="highlight-box">
1446 <p><strong>🎉 Status: PAID</strong><br>
1447 Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1448 </div>
1449
1450 <p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
1451
1452 <div class="divider"></div>
1453
1454 <p>Thank you for choosing our services!</p>
1455
1456 <p>Best regards,<br>
1457 <strong>{{company_name}}</strong><br>
1458 {{company_email}}</p>', 'easy-invoice'));
1459 }
1460 }
1461
1462 /**
1463 * Test email functionality
1464 */
1465 public function testEmail(): void {
1466 // Verify nonce
1467 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) {
1468 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
1469 }
1470
1471 // Check permissions
1472 if (!current_user_can('manage_options')) {
1473 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
1474 }
1475
1476 // Get test email address
1477 $test_email = sanitize_email($_POST['test_email'] ?? '');
1478 if (empty($test_email)) {
1479 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1480 }
1481
1482 // Test email sending
1483 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
1484 $result = $email_manager->testEmail($test_email);
1485
1486 if ($result['success']) {
1487 wp_send_json_success($result);
1488 } else {
1489 wp_send_json_error($result);
1490 }
1491 }
1492
1493 /**
1494 * Test payment reminder email functionality
1495 */
1496 public function testPaymentReminderEmail(): void {
1497 // Verify nonce
1498 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) {
1499 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
1500 }
1501
1502 // Check permissions
1503 if (!current_user_can('manage_options')) {
1504 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
1505 }
1506
1507 // Get test email address
1508 $test_email = sanitize_email($_POST['test_email'] ?? '');
1509 if (empty($test_email)) {
1510 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1511 }
1512
1513 // Get payment reminder settings
1514 $subject = get_option('easy_invoice_pro_payment_reminder_subject', __('A friendly reminder - Invoice #{{invoice_number}}', 'easy-invoice-pro'));
1515 $body = get_option('easy_invoice_pro_payment_reminder_body', '');
1516
1517 if (empty($body)) {
1518 wp_send_json_error(['message' => __('Payment reminder email template is empty. Please configure the email message first.', 'easy-invoice')]);
1519 }
1520
1521 // Create sample data for testing
1522 $sample_data = [
1523 'invoice_number' => 'TEST-001',
1524 'client_name' => 'Test Client',
1525 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1526 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1527 'total_amount' => '$1,000.00',
1528 'due_date' => date('Y-m-d', strtotime('+7 days')),
1529 ];
1530
1531 // Replace placeholders
1532 foreach ($sample_data as $placeholder => $value) {
1533 $subject = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $subject);
1534 $body = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $body);
1535 }
1536
1537 // Test email sending
1538 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
1539 $result = $email_manager->sendTestTemplateEmail($test_email, $subject, $body);
1540
1541 if ($result['success']) {
1542 wp_send_json_success($result);
1543 } else {
1544 wp_send_json_error($result);
1545 }
1546 }
1547
1548 /**
1549 * Test template email functionality
1550 */
1551 public function testTemplateEmail(): void {
1552 // Verify nonce
1553 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) {
1554 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
1555 }
1556
1557 // Check permissions
1558 if (!current_user_can('manage_options')) {
1559 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
1560 }
1561
1562 // Get test email address and template type
1563 $test_email = sanitize_email($_POST['test_email'] ?? '');
1564 $template_type = sanitize_text_field($_POST['template_type'] ?? '');
1565
1566 if (empty($test_email)) {
1567 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1568 }
1569
1570 if (empty($template_type)) {
1571 wp_send_json_error(['message' => __('Template type is required', 'easy-invoice')]);
1572 }
1573
1574 // Get the actual configured email settings
1575 $subject = '';
1576 $body = '';
1577
1578 switch ($template_type) {
1579 case 'invoice_available':
1580 $subject = get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1581 $body = get_option('easy_invoice_invoice_email_body', self::getInvoiceEmailBody());
1582 break;
1583 case 'quote_available':
1584 $subject = get_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'));
1585 $body = get_option('easy_invoice_quote_email_body', self::getQuoteEmailBody());
1586 break;
1587 case 'payment_received':
1588 $subject = get_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'));
1589 $body = get_option('easy_invoice_payment_email_body', self::getPaymentEmailBody());
1590 break;
1591 default:
1592 wp_send_json_error(['message' => __('Invalid template type', 'easy-invoice')]);
1593 }
1594
1595 // Ensure we have valid strings
1596 $subject = is_string($subject) ? $subject : '';
1597 $body = is_string($body) ? $body : '';
1598
1599 if (empty($subject) || empty($body)) {
1600 wp_send_json_error(['message' => __('Email template is empty or invalid', 'easy-invoice')]);
1601 }
1602
1603 // Create sample data for testing
1604 $sample_data = [
1605 'invoice_number' => 'TEST-001',
1606 'quote_number' => 'TEST-Q-001',
1607 'client_name' => 'Test Client',
1608 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1609 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1610 'total_amount' => '$1,000.00',
1611 'payment_amount' => '$1,000.00',
1612 'due_date' => date('Y-m-d', strtotime('+30 days')),
1613 'expiry_date' => date('Y-m-d', strtotime('+30 days')),
1614 'payment_date' => date('Y-m-d'),
1615 'payment_method' => 'Credit Card',
1616 'transaction_id' => 'TXN-' . uniqid(),
1617 'payment_terms' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
1618 ];
1619
1620 // Ensure all sample data values are strings
1621 foreach ($sample_data as $key => $value) {
1622 $sample_data[$key] = is_string($value) ? $value : (string) $value;
1623 }
1624
1625 // Replace placeholders in subject and body
1626 foreach ($sample_data as $placeholder => $value) {
1627 $subject = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $subject);
1628 $body = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $body);
1629 }
1630
1631 // Test email sending
1632 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
1633 $result = $email_manager->sendTestTemplateEmail($test_email, $subject, $body);
1634
1635 if ($result['success']) {
1636 wp_send_json_success($result);
1637 } else {
1638 wp_send_json_error($result);
1639 }
1640 }
1641
1642 /**
1643 * Check if quote adjust field should be shown
1644 *
1645 * @return bool True if adjust field should be shown
1646 */
1647 public static function shouldShowQuoteAdjustField(): bool {
1648 return get_option('easy_invoice_quote_show_adjust_field', 'yes') === 'yes';
1649 }
1650
1651 /**
1652 * Check if invoice adjust field should be shown
1653 *
1654 * @return bool True if adjust field should be shown
1655 */
1656 public static function shouldShowInvoiceAdjustField(): bool {
1657 return get_option('easy_invoice_invoice_show_adjust_field', 'yes') === 'yes';
1658 }
1659
1660 /**
1661 * Get invoice prefix
1662 *
1663 * @return string Invoice prefix
1664 */
1665 public static function getInvoicePrefix(): string {
1666 return get_option('easy_invoice_invoice_prefix', 'EIN_');
1667 }
1668
1669 /**
1670 * Get invoice starting number
1671 *
1672 * @return int Invoice starting number
1673 */
1674 public static function getInvoiceStartingNumber(): int {
1675 return (int) get_option('easy_invoice_invoice_starting_number', 0);
1676 }
1677
1678 /**
1679 * Get invoice terms and conditions
1680 *
1681 * @return string Invoice terms and conditions
1682 */
1683 public static function getInvoiceTermsConditions(): string {
1684 return get_option('easy_invoice_invoice_terms_conditions', __('Payment is due within 30 days from date of invoice', 'easy-invoice'));
1685 }
1686
1687 /**
1688 * Get invoice footer text
1689 *
1690 * @return string Invoice footer text
1691 */
1692 public static function getInvoiceFooterText(): string {
1693 return get_option('easy_invoice_invoice_footer_text', '');
1694 }
1695
1696 /**
1697 * Get quote prefix
1698 *
1699 * @return string Quote prefix
1700 */
1701 public static function getQuotePrefix(): string {
1702 return get_option('easy_invoice_quote_prefix', 'EIQN_');
1703 }
1704
1705 /**
1706 * Get quote starting number
1707 *
1708 * @return int Quote starting number
1709 */
1710 public static function getQuoteStartingNumber(): int {
1711 return (int) get_option('easy_invoice_quote_starting_number', 1);
1712 }
1713
1714 /**
1715 * Get quote terms and conditions
1716 *
1717 * @return string Quote terms and conditions
1718 */
1719 public static function getQuoteTermsConditions(): string {
1720 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'));
1721 }
1722
1723 /**
1724 * Get quote footer text
1725 *
1726 * @return string Quote footer text
1727 */
1728 public static function getQuoteFooterText(): string {
1729 return get_option('easy_invoice_quote_footer_text', __('Thanks for choosing Easy Invoice', 'easy-invoice'));
1730 }
1731
1732 /**
1733 * Check if quote accept button should be shown
1734 *
1735 * @return bool True if accept button should be shown
1736 */
1737 public static function shouldShowQuoteAcceptButton(): bool {
1738 return get_option('easy_invoice_quote_accept_button', 'yes') === 'yes';
1739 }
1740
1741 /**
1742 * Get quote accept action
1743 *
1744 * @return string Quote accept action
1745 */
1746 public static function getQuoteAcceptAction(): string {
1747 return get_option('easy_invoice_quote_accept_action', 'convert');
1748 }
1749
1750 /**
1751 * Get quote accept text
1752 *
1753 * @return string Quote accept text
1754 */
1755 public static function getQuoteAcceptText(): string {
1756 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'));
1757 }
1758
1759 /**
1760 * Get accepted quote message
1761 *
1762 * @return string Accepted quote message
1763 */
1764 public static function getAcceptedQuoteMessage(): string {
1765 return get_option('easy_invoice_quote_accepted_message', __('You\'ve confirmed the Quote.<br>We\'ll get in touch with you shortly.', 'easy-invoice'));
1766 }
1767
1768 /**
1769 * Check if decline reason is required
1770 *
1771 * @return bool True if decline reason is required
1772 */
1773 public static function isDeclineReasonRequired(): bool {
1774 return get_option('easy_invoice_quote_decline_reason_required', 'no') === 'yes';
1775 }
1776
1777 /**
1778 * Get declined quote message
1779 *
1780 * @return string Declined quote message
1781 */
1782 public static function getDeclinedQuoteMessage(): string {
1783 return get_option('easy_invoice_quote_declined_message', '');
1784 }
1785
1786 // Email Settings Helper Functions
1787
1788 /**
1789 * Check if invoice available email is enabled
1790 *
1791 * @return bool True if invoice email is enabled
1792 */
1793 public static function isInvoiceEmailEnabled(): bool {
1794 return get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes';
1795 }
1796
1797 /**
1798 * Get invoice email subject
1799 *
1800 * @return string Invoice email subject
1801 */
1802 public static function getInvoiceEmailSubject(): string {
1803 return get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1804 }
1805
1806 /**
1807 * Get invoice email body
1808 *
1809 * @return string Invoice email body
1810 */
1811 public static function getInvoiceEmailBody(): string {
1812 return get_option('easy_invoice_invoice_email_body', __('<h2>📄 Your Invoice is Ready</h2>
1813
1814 <p>Dear {{client_name}},</p>
1815
1816 <div class="highlight-box">
1817 <p><strong>Invoice #{{invoice_number}}</strong><br>
1818 <span class="amount-highlight">{{total_amount}}</span><br>
1819 Due Date: <strong>{{due_date}}</strong></p>
1820 </div>
1821
1822 <p>Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment.</p>
1823
1824 <div class="info-box">
1825 <p><strong>📋 Payment Details:</strong><br>
1826 • Invoice Number: {{invoice_number}}<br>
1827 • Total Amount: {{total_amount}}<br>
1828 • Due Date: {{due_date}}<br>
1829 • Payment Terms: {{payment_terms}}</p>
1830 </div>
1831
1832 <div class="warning-box">
1833 <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1834 </div>
1835
1836 <p>If you have any questions about this invoice, please do not hesitate to contact us.</p>
1837
1838 <div class="divider"></div>
1839
1840 <p>Thank you for your business!</p>
1841
1842 <p>Best regards,<br>
1843 <strong>{{company_name}}</strong><br>
1844 {{company_email}}</p>', 'easy-invoice'));
1845 }
1846
1847 /**
1848 * Check if quote available email is enabled
1849 *
1850 * @return bool True if quote email is enabled
1851 */
1852 public static function isQuoteEmailEnabled(): bool {
1853 return get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes';
1854 }
1855
1856 /**
1857 * Get quote email subject
1858 *
1859 * @return string Quote email subject
1860 */
1861 public static function getQuoteEmailSubject(): string {
1862 return get_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'));
1863 }
1864
1865 /**
1866 * Get quote email body
1867 *
1868 * @return string Quote email body
1869 */
1870 public static function getQuoteEmailBody(): string {
1871 return get_option('easy_invoice_quote_email_body', __('<h2>📋 Your Quote is Ready</h2>
1872
1873 <p>Dear {{client_name}},</p>
1874
1875 <div class="highlight-box">
1876 <p><strong>Quote #{{quote_number}}</strong><br>
1877 <span class="amount-highlight">{{total_amount}}</span><br>
1878 Valid Until: <strong>{{expiry_date}}</strong></p>
1879 </div>
1880
1881 <p>We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment.</p>
1882
1883 <div class="info-box">
1884 <p><strong>📋 Quote Summary:</strong><br>
1885 • Quote Number: {{quote_number}}<br>
1886 • Total Amount: {{total_amount}}<br>
1887 • Valid Until: {{expiry_date}}<br>
1888 • Terms: {{payment_terms}}</p>
1889 </div>
1890
1891 <div class="warning-box">
1892 <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
1893 </div>
1894
1895 <p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
1896
1897 <div class="divider"></div>
1898
1899 <p>We look forward to working with you!</p>
1900
1901 <p>Best regards,<br>
1902 <strong>{{company_name}}</strong><br>
1903 {{company_email}}</p>', 'easy-invoice'));
1904 }
1905
1906 /**
1907 * Check if payment received email is enabled
1908 *
1909 * @return bool True if payment email is enabled
1910 */
1911 public static function isPaymentEmailEnabled(): bool {
1912 return get_option('easy_invoice_payment_email_enabled', 'yes') === 'yes';
1913 }
1914
1915 /**
1916 * Get payment email subject
1917 *
1918 * @return string Payment email subject
1919 */
1920 public static function getPaymentEmailSubject(): string {
1921 return get_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'));
1922 }
1923
1924 /**
1925 * Get payment email body
1926 *
1927 * @return string Payment email body
1928 */
1929 public static function getPaymentEmailBody(): string {
1930 return get_option('easy_invoice_payment_email_body', __('<h2>�
1931 Payment Received - Thank You!</h2>
1932
1933 <p>Dear {{client_name}},</p>
1934
1935 <div class="success-box">
1936 <p><strong>Payment Confirmation</strong><br>
1937 Invoice #{{invoice_number}}<br>
1938 <span class="amount-highlight">{{payment_amount}}</span><br>
1939 Payment Date: <strong>{{payment_date}}</strong><br>
1940 Payment Method: <strong>{{payment_method}}</strong></p>
1941 </div>
1942
1943 <p>We have successfully received your payment. Thank you for your prompt payment!</p>
1944
1945 <div class="info-box">
1946 <p><strong>📊 Payment Details:</strong><br>
1947 Invoice Number: {{invoice_number}}<br>
1948 Amount Paid: {{payment_amount}}<br>
1949 Payment Date: {{payment_date}}<br>
1950 Payment Method: {{payment_method}}<br>
1951 Transaction ID: {{transaction_id}}</p>
1952 </div>
1953
1954 <div class="highlight-box">
1955 <p><strong>🎉 Status: PAID</strong><br>
1956 Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1957 </div>
1958
1959 <p>If you have any questions about this payment or need a receipt, please do not hesitate to contact us.</p>
1960
1961 <div class="divider"></div>
1962
1963 <p>Thank you for choosing our services!</p>
1964
1965 <p>Best regards,<br>
1966 <strong>{{company_name}}</strong><br>
1967 {{company_email}}</p>', 'easy-invoice'));
1968 }
1969
1970 // ========================================
1971 // TEXT SETTINGS HELPER FUNCTIONS
1972 // ========================================
1973 //
1974 // USAGE IN TEMPLATES:
1975 // Instead of: echo __('Invoice', 'easy-invoice');
1976 // Use: echo \EasyInvoice\Controllers\SettingsController::getTextInvoice();
1977 //
1978 // Example:
1979 // <h1>echo \EasyInvoice\Controllers\SettingsController::getTextInvoice();</h1>
1980 // <p>echo \EasyInvoice\Controllers\SettingsController::getTextFrom(); : echo $company_name;</p>
1981 // <p>echo \EasyInvoice\Controllers\SettingsController::getTextTo(); : echo $client_name;</p>
1982 //
1983
1984 /**
1985 * Get custom text setting with fallback to default
1986 *
1987 * @param string $key Text setting key
1988 * @param string $default Default text
1989 * @return string Custom text or default
1990 */
1991 public static function getTextSetting(string $key, string $default): string {
1992 $option_key = 'easy_invoice_text_' . $key;
1993 return get_option($option_key, $default);
1994 }
1995
1996 // Invoice Text Settings
1997 public static function getTextInvoice(): string {
1998 return self::getTextSetting('invoice', __('Invoice', 'easy-invoice'));
1999 }
2000
2001 public static function getTextInvoices(): string {
2002 return self::getTextSetting('invoices', __('Invoices', 'easy-invoice'));
2003 }
2004
2005 public static function getTextTo(): string {
2006 return self::getTextSetting('to', __('To', 'easy-invoice'));
2007 }
2008
2009 public static function getTextInvoiceNumber(): string {
2010 return self::getTextSetting('invoice_number', __('Invoice Number', 'easy-invoice'));
2011 }
2012
2013 public static function getTextInvoiceDate(): string {
2014 return self::getTextSetting('invoice_date', __('Invoice Date', 'easy-invoice'));
2015 }
2016
2017 public static function getTextDueDate(): string {
2018 return self::getTextSetting('due_date', __('Due Date', 'easy-invoice'));
2019 }
2020
2021 public static function getTextTotalDue(): string {
2022 return self::getTextSetting('total_due', __('Total Due', 'easy-invoice'));
2023 }
2024
2025 public static function getTextQty(): string {
2026 return self::getTextSetting('qty', __('Qty', 'easy-invoice'));
2027 }
2028
2029 public static function getTextService(): string {
2030 return self::getTextSetting('service', __('Service', 'easy-invoice'));
2031 }
2032
2033 public static function getTextRatePrice(): string {
2034 return self::getTextSetting('rate_price', __('Rate', 'easy-invoice'));
2035 }
2036
2037 public static function getTextAdjust(): string {
2038 return self::getTextSetting('adjust', __('Adjust', 'easy-invoice'));
2039 }
2040
2041 public static function getTextSubTotal(): string {
2042 return self::getTextSetting('sub_total', __('Sub Total', 'easy-invoice'));
2043 }
2044
2045 public static function getTextTotal(): string {
2046 return self::getTextSetting('total', __('Total', 'easy-invoice'));
2047 }
2048
2049 public static function getTextTax(): string {
2050 return self::getTextSetting('tax', __('Tax', 'easy-invoice'));
2051 }
2052
2053 public static function getTextDiscount(): string {
2054 return self::getTextSetting('discount', __('Discount', 'easy-invoice'));
2055 }
2056
2057 public static function getTextPrint(): string {
2058 return self::getTextSetting('print', __('Print', 'easy-invoice'));
2059 }
2060
2061 public static function getTextDownloadPdf(): string {
2062 return self::getTextSetting('download_pdf', __('Download as PDF', 'easy-invoice'));
2063 }
2064
2065 public static function getTextSendEmail(): string {
2066 return self::getTextSetting('send_email', __('Send Email', 'easy-invoice'));
2067 }
2068
2069 public static function getTextPayNow(): string {
2070 return self::getTextSetting('pay_now', __('Pay Now', 'easy-invoice'));
2071 }
2072
2073 // Quote Text Settings
2074 public static function getTextQuote(): string {
2075 return self::getTextSetting('quote', __('Quote', 'easy-invoice'));
2076 }
2077
2078 public static function getTextQuoteNumber(): string {
2079 return self::getTextSetting('quote_number', __('Quote Number', 'easy-invoice'));
2080 }
2081
2082 public static function getTextAcceptQuote(): string {
2083 return self::getTextSetting('accept_quote', __('Accept Quote', 'easy-invoice'));
2084 }
2085
2086 public static function getTextDeclineQuote(): string {
2087 return self::getTextSetting('decline_quote', __('Decline Quote', 'easy-invoice'));
2088 }
2089
2090 public static function getTextDeclineReason(): string {
2091 return self::getTextSetting('decline_reason', __('Reason for declining', 'easy-invoice'));
2092 }
2093
2094 public static function getTextValidUntil(): string {
2095 return self::getTextSetting('valid_until', __('Valid Until Date', 'easy-invoice'));
2096 }
2097
2098 public static function getTextQuoteDate(): string {
2099 return self::getTextSetting('quote_date', __('Quote Date', 'easy-invoice'));
2100 }
2101
2102 public static function getTextFrom(): string {
2103 return self::getTextSetting('from', __('From', 'easy-invoice'));
2104 }
2105
2106 /**
2107 * Get the actual date format string from the stored format identifier
2108 *
2109 * @param string $format_identifier The format identifier (us, uk, iso)
2110 * @return string The actual date format string
2111 */
2112 public static function getDateFormatString($format_identifier = null): string {
2113 if ($format_identifier === null) {
2114 $format_identifier = get_option('easy_invoice_date_format', 'us');
2115 }
2116
2117 switch ($format_identifier) {
2118 case 'uk':
2119 return 'd/m/Y';
2120 case 'iso':
2121 return 'Y-m-d';
2122 case 'us':
2123 default:
2124 return 'm/d/Y';
2125 }
2126 }
2127
2128 /**
2129 * Get the current date format string
2130 *
2131 * @return string The current date format string
2132 */
2133 public static function getCurrentDateFormat(): string {
2134 return self::getDateFormatString();
2135 }
2136
2137 /**
2138 * Format a date using the Easy Invoice date format setting
2139 *
2140 * @param string|int $date The date to format (timestamp or date string)
2141 * @return string The formatted date
2142 */
2143 public static function formatDate($date): string {
2144 $format = self::getCurrentDateFormat();
2145 $timestamp = is_numeric($date) ? $date : strtotime($date);
2146 return date_i18n($format, $timestamp);
2147 }
2148
2149 /**
2150 * AJAX handler for regenerating invoice numbers
2151 */
2152 public function ajaxRegenerateInvoiceNumbers() {
2153
2154 // Verify nonce
2155 $nonce = $_POST['nonce'] ?? $_POST['easy_invoice_settings_nonce'] ?? '';
2156 if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) {
2157 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
2158 }
2159
2160 // Check permissions
2161 if (!current_user_can('manage_options')) {
2162 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
2163 }
2164
2165 try {
2166 // Get the current next invoice number
2167 $current_next_number = intval(get_option('easy_invoice_next_invoice_number', 1));
2168 $prefix = get_option('easy_invoice_invoice_prefix', 'EIIN_');
2169
2170 // Start regeneration from the current next number
2171 $starting_number = $current_next_number;
2172
2173 // Get all invoices ordered by creation date
2174 $invoices = get_posts([
2175 'post_type' => 'easy_invoice',
2176 'post_status' => 'publish',
2177 'numberposts' => -1,
2178 'orderby' => 'date',
2179 'order' => 'ASC'
2180 ]);
2181
2182 if (empty($invoices)) {
2183 wp_send_json_success([
2184 'message' => __('No invoices found to regenerate', 'easy-invoice'),
2185 'regenerated_count' => 0
2186 ]);
2187 }
2188
2189 $regenerated_count = 0;
2190 $current_number = $starting_number;
2191
2192 foreach ($invoices as $invoice) {
2193 // Generate new invoice number
2194 $new_invoice_number = $prefix . str_pad($current_number, 6, '0', STR_PAD_LEFT);
2195
2196 // Update the invoice number
2197 update_post_meta($invoice->ID, '_easy_invoice_number', $new_invoice_number);
2198
2199 $regenerated_count++;
2200 $current_number++;
2201 }
2202
2203 // Update the next invoice number counter to continue from the last regenerated number + 1
2204 update_option('easy_invoice_next_invoice_number', $current_number);
2205
2206 wp_send_json_success([
2207 'message' => sprintf(__('Successfully regenerated %d invoice numbers', 'easy-invoice'), $regenerated_count),
2208 'regenerated_count' => $regenerated_count,
2209 'next_number' => $current_number
2210 ]);
2211
2212 } catch (Exception $e) {
2213 wp_send_json_error(['message' => __('Failed to regenerate invoice numbers', 'easy-invoice')]);
2214 }
2215 }
2216
2217 /**
2218 * AJAX handler for regenerating quote numbers
2219 */
2220 public function ajaxRegenerateQuoteNumbers() {
2221
2222 // Verify nonce
2223 $nonce = $_POST['nonce'] ?? $_POST['easy_invoice_settings_nonce'] ?? '';
2224 if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) {
2225 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
2226 }
2227
2228 // Check permissions
2229 if (!current_user_can('manage_options')) {
2230 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
2231 }
2232
2233 try {
2234 $current_next_number = intval(get_option('easy_invoice_next_quote_number', 1));
2235 $prefix = get_option('easy_invoice_quote_prefix', 'QT-');
2236
2237 $starting_number = $current_next_number;
2238
2239 $quotes = get_posts([
2240 'post_type' => 'easy_invoice_quote',
2241 'post_status' => 'publish',
2242 'numberposts' => -1,
2243 'orderby' => 'date',
2244 'order' => 'ASC',
2245 'meta_query' => [
2246 [
2247 'key' => '_easy_invoice_quote_number',
2248 'compare' => 'EXISTS'
2249 ]
2250 ]
2251 ]);
2252
2253 $regenerated_count = 0;
2254 $current_number = $starting_number;
2255
2256 foreach ($quotes as $quote_post) {
2257 $new_quote_number = $prefix . str_pad($current_number, 6, '0', STR_PAD_LEFT);
2258 update_post_meta($quote_post->ID, '_easy_invoice_quote_number', $new_quote_number);
2259 $regenerated_count++;
2260 $current_number++;
2261 }
2262
2263 update_option('easy_invoice_next_quote_number', $current_number);
2264
2265 wp_send_json_success([
2266 'message' => sprintf(__('Successfully regenerated %d quote numbers', 'easy-invoice'), $regenerated_count),
2267 'regenerated_count' => $regenerated_count,
2268 'next_number' => $current_number
2269 ]);
2270
2271 } catch (Exception $e) {
2272 wp_send_json_error(['message' => __('Failed to regenerate quote numbers', 'easy-invoice')]);
2273 }
2274 }
2275 }
2276