PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.21
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.21
2.4.3 2.4.4 2.4.2 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 All 60 releases
easy-invoice / includes / Controllers / SettingsController.php

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

2,253 lines 101.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 <p><strong>Shortcode:</strong> <code>[easy_quote_url number="{{quote_number}}" text="View Quote"]</code></p>
517 </div>
518
519 <div class="warning-box">
520 <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
521 </div>
522
523 <p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
524
525 <div class="divider"></div>
526
527 <p>We look forward to working with you!</p>
528
529 <p>Best regards,<br>
530 <strong>{{company_name}}</strong><br>
531 {{company_email}}</p>', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
532 'easy_invoice_quote_test_template' => [
533 'label' => __('Test Quote Email', 'easy-invoice'),
534 'type' => 'test_template_email',
535 'default' => '',
536 'col_span' => 'sm:col-span-6',
537 'description' => __('Send a test quote email to verify the template and configuration.', 'easy-invoice'),
538 'template_type' => 'quote_available'
539 ],
540 ]
541 ],
542 'payment_received' => [
543 'title' => __('Payment Received Email', 'easy-invoice'),
544 'description' => __('Configure email sent when a payment is received', 'easy-invoice'),
545 'fields' => [
546 'easy_invoice_payment_email_enabled' => ['label' => __('Enable Payment Received Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
547 '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'],
548 'easy_invoice_payment_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('<h2>�
549 Payment Received - Thank You!</h2>
550
551 <p>Dear {{client_name}},</p>
552
553 <div class="success-box">
554 <p><strong>Payment Confirmation</strong><br>
555 Invoice #{{invoice_number}}<br>
556 <span class="amount-highlight">{{payment_amount}}</span><br>
557 Payment Date: <strong>{{payment_date}}</strong><br>
558 Payment Method: <strong>{{payment_method}}</strong></p>
559 </div>
560
561 <p>We have successfully received your payment. Thank you for your prompt payment!</p>
562
563 <div class="info-box">
564 <p><strong>📊 Payment Details:</strong><br>
565 • Invoice Number: {{invoice_number}}<br>
566 • Amount Paid: {{payment_amount}}<br>
567 • Payment Date: {{payment_date}}<br>
568 • Payment Method: {{payment_method}}<br>
569 • Transaction ID: {{transaction_id}}</p>
570 </div>
571
572 <div class="highlight-box">
573 <p><strong>🎉 Status: PAID</strong><br>
574 Your payment has been processed and your account is now up to date. We appreciate your business!</p>
575 </div>
576
577 <p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
578
579 <div class="divider"></div>
580
581 <p>Thank you for choosing our services!</p>
582
583 <p>Best regards,<br>
584 <strong>{{company_name}}</strong><br>
585 {{company_email}}</p>', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
586 'easy_invoice_payment_test_template' => [
587 'label' => __('Test Payment Email', 'easy-invoice'),
588 'type' => 'test_template_email',
589 'default' => '',
590 'col_span' => 'sm:col-span-6',
591 'description' => __('Send a test payment email to verify the template and configuration.', 'easy-invoice'),
592 'template_type' => 'payment_received'
593 ],
594 ]
595 ],
596 ]
597 ],
598 'text_settings' => [
599 'title' => __('Text Settings', 'easy-invoice'),
600 'description' => __('Customize text labels for invoices and quotes. Perfect for multi-language support.', 'easy-invoice'),
601 '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">
602 <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"/>
603 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
604 <path d="M8 11H16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
605 <path d="M8 15H12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
606 <path d="M8 19H14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
607 </svg>',
608 'fields' => [
609 // Invoice Text Settings
610 'easy_invoice_text_invoice' => ['label' => __('Invoice', 'easy-invoice'), 'type' => 'text', 'default' => __('Invoice', 'easy-invoice'), 'col_span' => '', 'description' => __('Label for single invoice', 'easy-invoice')],
611 'easy_invoice_text_invoices' => ['label' => __('Invoices', 'easy-invoice'), 'type' => 'text', 'default' => __('Invoices', 'easy-invoice'), 'col_span' => '', 'description' => __('Label for multiple invoices', 'easy-invoice')],
612 'easy_invoice_text_to' => ['label' => __('To', 'easy-invoice'), 'type' => 'text', 'default' => __('To', 'easy-invoice'), 'col_span' => '', 'description' => __('To label on invoice/quote', 'easy-invoice')],
613 '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')],
614 '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')],
615 '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')],
616 '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')],
617 'easy_invoice_text_qty' => ['label' => __('Qty', 'easy-invoice'), 'type' => 'text', 'default' => __('Qty', 'easy-invoice'), 'col_span' => '', 'description' => __('Quantity label', 'easy-invoice')],
618 'easy_invoice_text_service' => ['label' => __('Service', 'easy-invoice'), 'type' => 'text', 'default' => __('Service', 'easy-invoice'), 'col_span' => '', 'description' => __('Service label', 'easy-invoice')],
619 'easy_invoice_text_rate_price' => ['label' => __('Rate', 'easy-invoice'), 'type' => 'text', 'default' => __('Rate', 'easy-invoice'), 'col_span' => '', 'description' => __('Rate or price label', 'easy-invoice')],
620 'easy_invoice_text_adjust' => ['label' => __('Adjust', 'easy-invoice'), 'type' => 'text', 'default' => __('Adjust', 'easy-invoice'), 'col_span' => '', 'description' => __('Adjust label', 'easy-invoice')],
621 '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')],
622 'easy_invoice_text_total' => ['label' => __('Total', 'easy-invoice'), 'type' => 'text', 'default' => __('Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Total label', 'easy-invoice')],
623 'easy_invoice_text_tax' => ['label' => __('Tax', 'easy-invoice'), 'type' => 'text', 'default' => __('Tax', 'easy-invoice'), 'col_span' => '', 'description' => __('Tax label', 'easy-invoice')],
624 'easy_invoice_text_discount' => ['label' => __('Discount', 'easy-invoice'), 'type' => 'text', 'default' => __('Discount', 'easy-invoice'), 'col_span' => '', 'description' => __('Discount label', 'easy-invoice')],
625 'easy_invoice_text_print' => ['label' => __('Print', 'easy-invoice'), 'type' => 'text', 'default' => __('Print', 'easy-invoice'), 'col_span' => '', 'description' => __('Print button text', 'easy-invoice')],
626 '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')],
627 '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')],
628 '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')],
629
630 // Quote Text Settings
631 'easy_invoice_text_quote' => ['label' => __('Quote', 'easy-invoice'), 'type' => 'text', 'default' => __('Quote', 'easy-invoice'), 'col_span' => '', 'description' => __('Label for single quote', 'easy-invoice')],
632 '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')],
633 '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')],
634 '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')],
635 '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')],
636 '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')],
637 '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')],
638 ]
639 ],
640 'advanced' => [
641 'title' => __('Advanced Settings', 'easy-invoice'),
642 'description' => __('Configure system preferences', 'easy-invoice'),
643 'icon' => 'fas fa-cog',
644 'fields' => [
645 'easy_invoice_date_format' => [
646 'label' => __('Date Format', 'easy-invoice'),
647 'type' => 'select',
648 'default' => 'us',
649 'options' => [
650 'us' => __('MM/DD/YYYY (US) - 01/15/2024', 'easy-invoice'),
651 'uk' => __('DD/MM/YYYY (UK) - 15/01/2024', 'easy-invoice'),
652 'iso' => __('YYYY-MM-DD (ISO) - 2024-01-15', 'easy-invoice'),
653 ],
654 'col_span' => 'sm:col-span-3'
655 ],
656
657 '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' ],
658 'easy_invoice_payment_reminder_days' => ['label' => __('Payment Reminder Days', 'easy-invoice'), 'type' => 'number', 'default' => 3, 'col_span' => 'sm:col-span-3'],
659 ]
660 ],
661 ];
662
663 // Cache and filter the config
664 $this->config_cache = apply_filters('easy_invoice_settings_fields_config', $config);
665
666 return $this->config_cache;
667 }
668
669 /**
670 * Get all gateway settings configurations
671 *
672 * @return array Gateway settings configs
673 */
674 private function getGatewaySettingsConfigs(): array {
675 $gateway_configs = [];
676
677 try {
678 // Get gateway manager from the main plugin instance
679 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
680 $gateways = $gateway_manager->getGateways();
681
682 // Collect settings from each gateway
683 foreach ($gateways as $gateway_id => $gateway) {
684 try {
685 $gateway_configs[$gateway_id] = $gateway->getSettingsConfig();
686 } catch (\Exception $e) {
687 // Log the error but continue with other gateways
688 $gateway_configs[$gateway_id] = [];
689 }
690 }
691 } catch (\Exception $e) {
692 }
693
694 return $gateway_configs;
695 }
696
697 /**
698 * Prepare settings for display with proper filtering
699 *
700 * @param array $settings The settings array
701 * @return array The filtered settings
702 */
703 public function prepareSettingsForDisplay($settings) {
704 return apply_filters('easy_invoice_settings_for_display', $settings);
705 }
706
707 /**
708 * Display method implementation
709 *
710 * @param array $args Display arguments
711 * @return void
712 */
713 public function display(array $args = []) {
714 $page = isset($args['page']) ? $args['page'] : '';
715 $subsection = isset($args['subsection']) ? $args['subsection'] : '';
716
717 // Clear cache to ensure new fields are recognized
718 $this->clearCache();
719
720 switch ($page) {
721 case PagesSlugs::SETTINGS:
722 if (!empty($subsection)) {
723 // Handle email settings subsections
724 $this->displayEmailSettingsPage($subsection);
725 } else {
726 $this->displaySettingsPage();
727 }
728 break;
729
730 default:
731 $this->displaySettingsPage();
732 break;
733 }
734 }
735
736 /**
737 * Display email settings page for specific subsection
738 *
739 * @param string $subsection The subsection to display
740 * @return void
741 */
742 protected function displayEmailSettingsPage($subsection) {
743 // Get settings configuration
744 $settings_config = $this->get_settings_fields_config();
745 $settings = $this->getSettings();
746
747 // Filter to show only email settings
748 $email_config = $settings_config['email'] ?? [];
749
750 // Map subsection to the appropriate subsection in email config
751 $subsection_mapping = [
752 PagesSlugs::EMAIL_SETTINGS_GENERAL => 'general',
753 PagesSlugs::EMAIL_SETTINGS_INVOICE => 'invoice_available',
754 PagesSlugs::EMAIL_SETTINGS_QUOTE => 'quote_available',
755 PagesSlugs::EMAIL_SETTINGS_PAYMENT => 'payment_received',
756 PagesSlugs::EMAIL_SETTINGS_PAYMENT_REMINDER => 'payment_reminder',
757 ];
758
759 $target_subsection = $subsection_mapping[$subsection] ?? 'general';
760
761 // Create a modified config with only the target subsection
762 $filtered_config = [
763 'email' => [
764 'title' => __('Email Settings', 'easy-invoice'),
765 'description' => __('Configure email sending options and templates', 'easy-invoice'),
766 'icon' => 'fas fa-envelope',
767 'subsections' => [
768 $target_subsection => $email_config['subsections'][$target_subsection] ?? []
769 ]
770 ]
771 ];
772
773 // Include the settings page template with filtered config
774 include EASY_INVOICE_PLUGIN_DIR . 'templates/settings-page.php';
775 }
776
777 /**
778 * Display settings page
779 *
780 * @return void
781 */
782 protected function displaySettingsPage() {
783 $settings = $this->getSettings();
784
785 // Allow developers to modify settings before display
786 $settings = apply_filters('easy_invoice_before_display_settings', $settings);
787
788 // Ensure payment methods are set (this is critical for gateway checkboxes)
789 if (!isset($settings['easy_invoice_payment_methods']) || !is_array($settings['easy_invoice_payment_methods'])) {
790 $settings['easy_invoice_payment_methods'] = get_option('easy_invoice_payment_methods', []);
791 }
792
793 // Ensure gateway order is set
794 if (!isset($settings['easy_invoice_payment_gateway_order']) || !is_array($settings['easy_invoice_payment_gateway_order'])) {
795 $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []);
796 }
797
798 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
799 $all_gateways_unsorted = $gateway_manager->getGateways();
800 $gateway_order_option = $settings['easy_invoice_payment_gateway_order'];
801 $payment_methods_enabled = $settings['easy_invoice_payment_methods'];
802
803 $all_gateways_sorted = [];
804 if (!empty($gateway_order_option)) {
805 foreach ($gateway_order_option as $gateway_id) {
806 if (isset($all_gateways_unsorted[$gateway_id])) {
807 $all_gateways_sorted[$gateway_id] = $all_gateways_unsorted[$gateway_id];
808 unset($all_gateways_unsorted[$gateway_id]);
809 }
810 }
811 }
812 $all_gateways_sorted = array_merge($all_gateways_sorted, $all_gateways_unsorted);
813
814 $settings_config = $this->get_settings_fields_config();
815
816 // Prepare template variables
817 $template_args = apply_filters('easy_invoice_settings_template_args', [
818 'settings' => $settings,
819 'settings_config' => $settings_config,
820 'all_gateways_sorted' => $all_gateways_sorted,
821 'payment_methods_enabled' => $payment_methods_enabled
822 ]);
823
824 // Display the template with template args
825 $this->displayTemplate(
826 apply_filters('easy_invoice_settings_template_path', EASY_INVOICE_PLUGIN_DIR . 'templates/settings-page.php'),
827 $template_args
828 );
829
830 // Action for plugins to add their own content after the settings page
831 do_action('easy_invoice_after_settings_page');
832 }
833
834 /**
835 * Save payment settings
836 *
837 * @param array $posted_settings Posted settings
838 * @param array $response Response array
839 * @return void
840 */
841 private function savePaymentSettings($posted_settings, &$response) {
842 // Handle payment methods
843 if (isset($posted_settings['easy_invoice_payment_methods']) && is_array($posted_settings['easy_invoice_payment_methods'])) {
844 $sanitized_methods = array_map('sanitize_key', $posted_settings['easy_invoice_payment_methods']);
845 update_option('easy_invoice_payment_methods', $sanitized_methods);
846 $response['saved_options']['easy_invoice_payment_methods'] = $sanitized_methods;
847 } else {
848 update_option('easy_invoice_payment_methods', []);
849 $response['saved_options']['easy_invoice_payment_methods'] = [];
850 }
851
852 // Handle gateway order
853 if (isset($posted_settings['easy_invoice_gateway_order']) && is_array($posted_settings['easy_invoice_gateway_order'])) {
854 $gateway_order = array_map('sanitize_key', $posted_settings['easy_invoice_gateway_order']);
855 update_option('easy_invoice_payment_gateway_order', $gateway_order);
856 $response['saved_options']['easy_invoice_gateway_order'] = $gateway_order;
857 } else {
858 update_option('easy_invoice_payment_gateway_order', []);
859 $response['saved_options']['easy_invoice_gateway_order'] = [];
860 }
861
862 // Handle gateway display names
863 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
864 $all_gateways = $gateway_manager->getGateways();
865
866 foreach ($all_gateways as $gateway_id => $gateway) {
867 $display_name_key = 'easy_invoice_gateway_display_name_' . $gateway_id;
868 if (isset($posted_settings[$display_name_key])) {
869 $display_name = wp_strip_all_tags(wp_unslash($posted_settings[$display_name_key]));
870 update_option($display_name_key, $display_name);
871 $response['saved_options'][$display_name_key] = $display_name;
872 }
873 }
874
875 // Handle gateway-specific settings
876 try {
877 $gateway_configs = $this->getGatewaySettingsConfigs();
878 foreach ($gateway_configs as $gateway_id => $gateway_config) {
879 if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) {
880 foreach ($gateway_config['fields'] as $option_key => $field_config) {
881 if (isset($posted_settings[$option_key])) {
882 $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response);
883 } elseif ($field_config['type'] === 'checkbox') {
884 // Checkboxes not in POST are considered unchecked
885 update_option($option_key, 'no');
886 $response['saved_options'][$option_key] = 'no';
887 }
888 }
889 }
890 }
891 } catch (\Exception $e) {
892 throw $e; // Re-throw to be caught by the main try-catch
893 }
894 }
895
896 /**
897 * Save settings
898 *
899 * @return void
900 */
901 public function saveSettings() {
902 // Verify nonce
903 if (!wp_verify_nonce($_POST['easy_invoice_settings_nonce'], 'easy_invoice_settings')) {
904 wp_die(__('Security check failed', 'easy-invoice'));
905 }
906
907
908 // Check permissions
909 if (!current_user_can('manage_options')) {
910 wp_die(__('You do not have permission to perform this action', 'easy-invoice'));
911 }
912
913 $posted_settings = $_POST['settings'] ?? [];
914
915 $settings_config = $this->get_settings_fields_config();
916 $response = [
917 'success' => true,
918 'message' => __('Settings saved successfully', 'easy-invoice'),
919 'saved_options' => []
920 ];
921
922 try {
923 // Process each section
924 foreach ($settings_config as $section_id => $section_data) {
925 if (isset($section_data['fields']) && is_array($section_data['fields'])) {
926 // Handle regular fields
927 foreach ($section_data['fields'] as $option_key => $field_config) {
928 if (isset($posted_settings[$option_key])) {
929 $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response);
930 } elseif ($field_config['type'] === 'checkbox') {
931 // Checkboxes not in POST are considered unchecked
932 update_option($option_key, 'no');
933 $response['saved_options'][$option_key] = 'no';
934 }
935 }
936 } else if (isset($section_data['subsections']) && is_array($section_data['subsections'])) {
937 // Handle sections with subsections (like invoice, quote, email, etc.)
938 foreach ($section_data['subsections'] as $subsection_id => $subsection_data) {
939 if (isset($subsection_data['fields']) && is_array($subsection_data['fields'])) {
940 foreach ($subsection_data['fields'] as $option_key => $field_config) {
941 if (isset($posted_settings[$option_key])) {
942 $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response);
943 } elseif ($field_config['type'] === 'checkbox') {
944 // Checkboxes not in POST are considered unchecked
945 update_option($option_key, 'no');
946 $response['saved_options'][$option_key] = 'no';
947 }
948 }
949 }
950 }
951 }
952 }
953
954 // Handle special sections like payment methods
955 if (isset($settings_config['payment']) && isset($settings_config['payment']['is_special_section'])) {
956 $this->savePaymentSettings($posted_settings, $response);
957 }
958
959 // Clear any caches
960 wp_cache_flush();
961
962 // Clear settings cache
963 $this->settings_cache = null;
964
965 // Log the save action
966 $this->log('Settings saved successfully by user: ' . get_current_user_id());
967
968 } catch (\Exception $e) {
969 $response['success'] = false;
970 $response['message'] = __('Error saving settings: ', 'easy-invoice') . $e->getMessage();
971 $this->log('Error saving settings: ' . $e->getMessage());
972
973 }
974
975 // Clear settings cache to ensure new fields are recognized
976 $this->clearCache();
977
978 // Send JSON response
979 wp_send_json($response);
980 }
981
982 /**
983 * Sanitize and save a single setting
984 *
985 * @param string $option_key The option key
986 * @param array $field_config The field configuration
987 * @param mixed $value The value to sanitize and save
988 * @param array &$response The response array to update
989 * @return void
990 */
991 private function sanitizeAndSaveSetting($option_key, $field_config, $value, &$response) {
992 if (in_array($option_key, [
993 'easy_invoice_quote_accept_text',
994 'easy_invoice_quote_accepted_message',
995 'easy_invoice_quote_declined_message'
996 ])) {
997 }
998
999 // Unslash the value to prevent double-escaping
1000 $value = wp_unslash($value);
1001
1002 // Allow pre-sanitization filters
1003 $value = apply_filters('easy_invoice_pre_sanitize_option', $value, $option_key, $field_config);
1004
1005 // Sanitize based on field type
1006 switch ($field_config['type']) {
1007 case 'email':
1008 $value = sanitize_email($value);
1009 break;
1010 case 'url':
1011 $value = esc_url_raw($value);
1012 break;
1013 case 'textarea':
1014 // Use wp_kses_post for textarea to allow safe HTML while preventing double-escaping
1015 $value = wp_kses_post($value);
1016 break;
1017 case 'wp_editor':
1018 // For wp_editor content, use WordPress's built-in sanitization
1019 if (is_string($value)) {
1020 // Use wp_kses_post which expects unslashed data
1021 $value = wp_kses_post($value);
1022 }
1023 break;
1024 case 'number':
1025 if (isset($field_config['step']) && strpos((string)$field_config['step'], '.') !== false) {
1026 $value = floatval(wp_strip_all_tags(easy_invoice_str_replace(',', '.', $value)));
1027 } elseif (isset($field_config['min']) && intval($field_config['min']) < 0) {
1028 $value = intval($value);
1029 } else {
1030 $value = absint($value);
1031 }
1032 break;
1033 case 'checkbox':
1034 // For checkboxes, we expect 'yes' or 'no' values
1035 $value = ($value === 'yes' || $value === '1' || $value === true) ? 'yes' : 'no';
1036 break;
1037 case 'select':
1038 $value = sanitize_key($value);
1039 // Ensure currency codes are always uppercase
1040 if ($option_key === 'easy_invoice_currency_code') {
1041 $value = strtoupper($value);
1042 }
1043 break;
1044 case 'multiselect':
1045 // For multiselect, ensure it's an array and sanitize each value
1046 if (!is_array($value)) {
1047 $value = [];
1048 } else {
1049 $value = array_map('sanitize_key', $value);
1050 }
1051 break;
1052 default:
1053 // For regular text fields, use wp_strip_all_tags to prevent double-escaping
1054 $value = wp_strip_all_tags($value);
1055 break;
1056 }
1057
1058 // Allow post-sanitization value modification
1059 $value = apply_filters("easy_invoice_sanitize_option_{$option_key}", $value, $field_config);
1060 $value = apply_filters('easy_invoice_sanitize_option', $value, $option_key, $field_config);
1061
1062 // Save the setting normally
1063 update_option($option_key, $value);
1064 $response['saved_options'][$option_key] = $value;
1065 }
1066
1067 /**
1068 * Get all settings for the plugin
1069 *
1070 * @param bool $use_cache Whether to use cached settings
1071 * @return array The settings array
1072 */
1073 public function getSettings($use_cache = true): array {
1074 // Return cached settings if available
1075 if ($use_cache && is_array($this->settings_cache)) {
1076 return $this->settings_cache;
1077 }
1078
1079 $settings = [];
1080 $config = $this->get_settings_fields_config();
1081
1082 foreach ($config as $section_id => $section_data) {
1083 if ($section_id === 'payment' && isset($section_data['gateways'])) {
1084 // Handle gateway fields specifically
1085 foreach ($section_data['gateways'] as $gateway_id => $gateway_config) {
1086 if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) {
1087 foreach ($gateway_config['fields'] as $option_key => $field_data) {
1088 $default_value = $field_data['default'] ?? '';
1089 $settings[$option_key] = get_option($option_key, $default_value);
1090 }
1091 }
1092 }
1093 }
1094 else if (isset($section_data['subsections']) && is_array($section_data['subsections'])) {
1095 // Handle sections with subsections (like email, invoice, quote)
1096 foreach ($section_data['subsections'] as $subsection_id => $subsection_data) {
1097 if (isset($subsection_data['fields']) && is_array($subsection_data['fields'])) {
1098 foreach ($subsection_data['fields'] as $option_key => $field_data) {
1099 $default_value = $field_data['default'] ?? '';
1100
1101 // Handle special defaults
1102 if ($option_key === 'easy_invoice_email_from_name' && $default_value === get_bloginfo('name')) {
1103 $default_value = get_bloginfo('name');
1104 }
1105 if ($option_key === 'easy_invoice_email_from_address' && $default_value === get_bloginfo('admin_email')) {
1106 $default_value = get_bloginfo('admin_email');
1107 }
1108
1109 // Check if field has a value_callback function
1110 if (isset($field_data['value_callback']) && is_callable($field_data['value_callback'])) {
1111 $settings[$option_key] = $field_data['value_callback']();
1112 } else {
1113 // Get option value or default
1114 $settings[$option_key] = get_option($option_key, $default_value);
1115 }
1116 }
1117 }
1118 }
1119 }
1120 else if (isset($section_data['fields']) && is_array($section_data['fields'])) {
1121 foreach ($section_data['fields'] as $option_key => $field_data) {
1122 $default_value = $field_data['default'] ?? '';
1123
1124 // Handle special defaults
1125 if ($option_key === 'easy_invoice_email_from_name' && $default_value === get_bloginfo('name')) {
1126 $default_value = get_bloginfo('name');
1127 }
1128 if ($option_key === 'easy_invoice_email_from_address' && $default_value === get_bloginfo('admin_email')) {
1129 $default_value = get_bloginfo('admin_email');
1130 }
1131
1132 // Check if field has a value_callback function
1133 if (isset($field_data['value_callback']) && is_callable($field_data['value_callback'])) {
1134 $settings[$option_key] = $field_data['value_callback']();
1135 } else {
1136 // Get option value or default
1137 $settings[$option_key] = get_option($option_key, $default_value);
1138 }
1139 }
1140 }
1141 }
1142
1143 // Add special array settings
1144 $settings['easy_invoice_payment_methods'] = get_option('easy_invoice_payment_methods', []);
1145 $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []);
1146
1147 // Cache settings
1148 $this->settings_cache = $settings;
1149
1150 // Allow third party to add/modify settings
1151 return apply_filters('easy_invoice_settings', $settings);
1152 }
1153
1154 /**
1155 * Log debug information
1156 *
1157 * @param string $message The message to log
1158 * @return void
1159 */
1160 protected function log(string $message): void {
1161
1162 }
1163
1164 /**
1165 * Initialize default settings
1166 */
1167 public function initializeSettings() {
1168 // Initialize invoice number settings if not already set
1169 if (!get_option('easy_invoice_invoice_prefix')) {
1170 update_option('easy_invoice_invoice_prefix', 'INV-');
1171 }
1172
1173 if (!get_option('easy_invoice_next_invoice_number')) {
1174 update_option('easy_invoice_next_invoice_number', 1);
1175 }
1176
1177 // Initialize other default settings
1178 if (!get_option('easy_invoice_invoice_due_days')) {
1179 update_option('easy_invoice_invoice_due_days', 30);
1180 }
1181
1182 if (!get_option('easy_invoice_currency_code')) {
1183 update_option('easy_invoice_currency_code', 'USD');
1184 }
1185
1186 if (!get_option('easy_invoice_currency_symbol')) {
1187 update_option('easy_invoice_currency_symbol', '$');
1188 }
1189
1190 if (!get_option('easy_invoice_currency_position')) {
1191 update_option('easy_invoice_currency_position', 'left');
1192 }
1193
1194 if (!get_option('easy_invoice_currency_symbol_type')) {
1195 update_option('easy_invoice_currency_symbol_type', 'symbol');
1196 }
1197
1198 if (!get_option('easy_invoice_decimal_separator')) {
1199 update_option('easy_invoice_decimal_separator', '.');
1200 }
1201
1202 if (!get_option('easy_invoice_thousands_separator')) {
1203 update_option('easy_invoice_thousands_separator', ',');
1204 }
1205
1206 if (!get_option('easy_invoice_decimal_precision')) {
1207 update_option('easy_invoice_decimal_precision', 2);
1208 }
1209
1210 // Initialize date format setting
1211 if (!get_option('easy_invoice_date_format')) {
1212 update_option('easy_invoice_date_format', 'us');
1213 }
1214
1215 // Fix legacy date format values
1216 $current_date_format = get_option('easy_invoice_date_format');
1217 if ($current_date_format === 'mdy' || $current_date_format === 'm/d/Y') {
1218 update_option('easy_invoice_date_format', 'us');
1219 } elseif ($current_date_format === 'd/m/Y') {
1220 update_option('easy_invoice_date_format', 'uk');
1221 } elseif ($current_date_format === 'Y-m-d') {
1222 update_option('easy_invoice_date_format', 'iso');
1223 }
1224
1225 // Initialize email settings
1226 if (!get_option('easy_invoice_email_from_name')) {
1227 update_option('easy_invoice_email_from_name', get_bloginfo('name'));
1228 }
1229
1230 if (!get_option('easy_invoice_email_from_address')) {
1231 update_option('easy_invoice_email_from_address', get_bloginfo('admin_email'));
1232 }
1233
1234 if (!get_option('easy_invoice_enable_email_styling')) {
1235 update_option('easy_invoice_enable_email_styling', 'yes');
1236 }
1237
1238 if (!get_option('easy_invoice_bcc_admin')) {
1239 update_option('easy_invoice_bcc_admin', 'no');
1240 }
1241
1242 if (!get_option('easy_invoice_admin_email')) {
1243 update_option('easy_invoice_admin_email', get_option('admin_email'));
1244 }
1245
1246 // Initialize Text Settings
1247 $text_settings = [
1248 'easy_invoice_text_invoice' => __('Invoice', 'easy-invoice'),
1249 'easy_invoice_text_invoices' => __('Invoices', 'easy-invoice'),
1250 'easy_invoice_text_from' => __('From', 'easy-invoice'),
1251 'easy_invoice_text_to' => __('To', 'easy-invoice'),
1252 'easy_invoice_text_invoice_number' => __('Invoice Number', 'easy-invoice'),
1253 'easy_invoice_text_order_number' => __('Order Number', 'easy-invoice'),
1254 'easy_invoice_text_invoice_date' => __('Invoice Date', 'easy-invoice'),
1255 'easy_invoice_text_due_date' => __('Due Date', 'easy-invoice'),
1256 'easy_invoice_text_total_due' => __('Total Due', 'easy-invoice'),
1257 'easy_invoice_text_qty' => __('Qty', 'easy-invoice'),
1258 'easy_invoice_text_service' => __('Service', 'easy-invoice'),
1259 'easy_invoice_text_rate_price' => __('Rate', 'easy-invoice'),
1260 'easy_invoice_text_adjust' => __('Adjust', 'easy-invoice'),
1261 'easy_invoice_text_sub_total' => __('Sub Total', 'easy-invoice'),
1262 'easy_invoice_text_total' => __('Total', 'easy-invoice'),
1263 'easy_invoice_text_tax' => __('Tax', 'easy-invoice'),
1264 'easy_invoice_text_discount' => __('Discount', 'easy-invoice'),
1265 'easy_invoice_text_page' => __('Page', 'easy-invoice'),
1266 'easy_invoice_text_print' => __('Print', 'easy-invoice'),
1267 'easy_invoice_text_download_pdf' => __('Download as PDF', 'easy-invoice'),
1268 'easy_invoice_text_send_email' => __('Send Email', 'easy-invoice'),
1269 'easy_invoice_text_pay_now' => __('Pay Now', 'easy-invoice'),
1270 'easy_invoice_text_proceed_payment' => __('Proceed to payment', 'easy-invoice'),
1271 'easy_invoice_text_payment_gateway' => __('Invoice Payment Gateway', 'easy-invoice'),
1272 'easy_invoice_text_quote' => __('Quote', 'easy-invoice'),
1273 'easy_invoice_text_quotes' => __('Quotes', 'easy-invoice'),
1274 'easy_invoice_text_quote_number' => __('Quote Number', 'easy-invoice'),
1275 'easy_invoice_text_accept_quote' => __('Accept Quote', 'easy-invoice'),
1276 'easy_invoice_text_decline_quote' => __('Decline Quote', 'easy-invoice'),
1277 'easy_invoice_text_decline_reason' => __('Reason for declining', 'easy-invoice'),
1278 'easy_invoice_text_quote_amount' => __('Quote Amount', 'easy-invoice'),
1279 'easy_invoice_text_valid_until' => __('Valid Until Date', 'easy-invoice'),
1280 'easy_invoice_text_quote_date' => __('Quote Date', 'easy-invoice'),
1281 'easy_invoice_text_available' => __('Available', 'easy-invoice'),
1282 'easy_invoice_text_draft' => __('Draft', 'easy-invoice'),
1283 'easy_invoice_text_overdue' => __('Overdue', 'easy-invoice'),
1284 'easy_invoice_text_paid' => __('Paid', 'easy-invoice'),
1285 'easy_invoice_text_unpaid' => __('Unpaid', 'easy-invoice'),
1286 'easy_invoice_text_cancelled' => __('Cancelled', 'easy-invoice'),
1287 ];
1288
1289 foreach ($text_settings as $option_key => $default_value) {
1290 if (!get_option($option_key)) {
1291 update_option($option_key, $default_value);
1292 }
1293 }
1294
1295 if (!get_option('easy_invoice_invoice_email_enabled')) {
1296 update_option('easy_invoice_invoice_email_enabled', 'yes');
1297 }
1298
1299 if (!get_option('easy_invoice_invoice_email_subject')) {
1300 update_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1301 }
1302
1303 if (!get_option('easy_invoice_invoice_email_body')) {
1304 update_option('easy_invoice_invoice_email_body', __('<h2>📄 Your Invoice is Ready</h2>
1305
1306 <p>Dear {{client_name}},</p>
1307
1308 <div class="highlight-box">
1309 <p><strong>Invoice #{{invoice_number}}</strong><br>
1310 <span class="amount-highlight">{{total_amount}}</span><br>
1311 Due Date: <strong>{{due_date}}</strong></p>
1312 </div>
1313
1314 <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>
1315
1316 <div class="info-box">
1317 <p><strong>📋 Payment Details:</strong><br>
1318 • Invoice Number: {{invoice_number}}<br>
1319 • Total Amount: {{total_amount}}<br>
1320 • Due Date: {{due_date}}<br>
1321 • Payment Terms: {{payment_terms}}</p>
1322 </div>
1323
1324 <div class="highlight-box">
1325 <p><strong>🔗 View Invoice Online:</strong><br>
1326 <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1327 <p><strong>Shortcode:</strong> <code>[easy_invoice_url number="{{invoice_number}}" text="View Invoice"]</code></p>
1328 </div>
1329
1330 <div class="warning-box">
1331 <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1332 </div>
1333
1334 <p>If you have any questions about this invoice, please don\'t hesitate to contact us.</p>
1335
1336 <div class="divider"></div>
1337
1338 <p>Thank you for your business!</p>
1339
1340 <p>Best regards,<br>
1341 <strong>{{company_name}}</strong><br>
1342 {{company_email}}</p>', 'easy-invoice'));
1343 }
1344
1345 if (!get_option('easy_invoice_quote_email_enabled')) {
1346 update_option('easy_invoice_quote_email_enabled', 'yes');
1347 }
1348
1349 if (!get_option('easy_invoice_quote_email_subject')) {
1350 update_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'));
1351 }
1352
1353 if (!get_option('easy_invoice_quote_email_body')) {
1354 update_option('easy_invoice_quote_email_body', __('<h2>Your Quote is Ready</h2>
1355
1356 <p>Dear {{client_name}},</p>
1357
1358 <div class="highlight-box">
1359 <p><strong>Quote #{{quote_number}}</strong><br>
1360 Total Amount: <strong>{{total_amount}}</strong><br>
1361 Valid Until: <strong>{{expiry_date}}</strong></p>
1362 </div>
1363
1364 <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>
1365
1366 <div class="info-box">
1367 <p><strong>Quote Summary:</strong><br>
1368 • Quote Number: {{quote_number}}<br>
1369 • Total Amount: {{total_amount}}<br>
1370 • Valid Until: {{expiry_date}}<br>
1371 • Terms: {{payment_terms}}</p>
1372 </div>
1373
1374 <div class="highlight-box">
1375 <p><strong>🔗 View Quote Online:</strong><br>
1376 <a href="{{quote_url}}" style="color: #3b82f6; text-decoration: underline;">{{quote_url}}</a></p>
1377 <p><strong>Shortcode:</strong> <code>[easy_quote_url number="{{quote_number}}" text="View Quote"]</code></p>
1378 </div>
1379
1380 <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>
1381
1382 <p>We look forward to working with you!</p>
1383
1384 <p>Best regards,<br>
1385 <strong>{{company_name}}</strong><br>
1386 {{company_email}}</p>', 'easy-invoice'));
1387 }
1388
1389 if (!get_option('easy_invoice_payment_email_enabled')) {
1390 update_option('easy_invoice_payment_email_enabled', 'yes');
1391 }
1392
1393 if (!get_option('easy_invoice_payment_email_subject')) {
1394 update_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'));
1395 }
1396
1397 if (!get_option('easy_invoice_payment_email_body')) {
1398 update_option('easy_invoice_payment_email_body', __('<h2>�
1399 Payment Received - Thank You!</h2>
1400
1401 <p>Dear {{client_name}},</p>
1402
1403 <div class="success-box">
1404 <p><strong>Payment Confirmation</strong><br>
1405 Invoice #{{invoice_number}}<br>
1406 <span class="amount-highlight">{{payment_amount}}</span><br>
1407 Payment Date: <strong>{{payment_date}}</strong><br>
1408 Payment Method: <strong>{{payment_method}}</strong></p>
1409 </div>
1410
1411 <p>We have successfully received your payment. Thank you for your prompt payment!</p>
1412
1413 <div class="info-box">
1414 <p><strong>📊 Payment Details:</strong><br>
1415 • Invoice Number: {{invoice_number}}<br>
1416 • Amount Paid: {{payment_amount}}<br>
1417 • Payment Date: {{payment_date}}<br>
1418 • Payment Method: {{payment_method}}<br>
1419 • Transaction ID: {{transaction_id}}</p>
1420 </div>
1421
1422 <div class="highlight-box">
1423 <p><strong>🎉 Status: PAID</strong><br>
1424 Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1425 </div>
1426
1427 <p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
1428
1429 <div class="divider"></div>
1430
1431 <p>Thank you for choosing our services!</p>
1432
1433 <p>Best regards,<br>
1434 <strong>{{company_name}}</strong><br>
1435 {{company_email}}</p>', 'easy-invoice'));
1436 }
1437 }
1438
1439 /**
1440 * Test email functionality
1441 */
1442 public function testEmail(): void {
1443 // Verify nonce
1444 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) {
1445 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
1446 }
1447
1448 // Check permissions
1449 if (!current_user_can('manage_options')) {
1450 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
1451 }
1452
1453 // Get test email address
1454 $test_email = sanitize_email($_POST['test_email'] ?? '');
1455 if (empty($test_email)) {
1456 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1457 }
1458
1459 // Test email sending
1460 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
1461 $result = $email_manager->testEmail($test_email);
1462
1463 if ($result['success']) {
1464 wp_send_json_success($result);
1465 } else {
1466 wp_send_json_error($result);
1467 }
1468 }
1469
1470 /**
1471 * Test payment reminder email functionality
1472 */
1473 public function testPaymentReminderEmail(): void {
1474 // Verify nonce
1475 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) {
1476 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
1477 }
1478
1479 // Check permissions
1480 if (!current_user_can('manage_options')) {
1481 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
1482 }
1483
1484 // Get test email address
1485 $test_email = sanitize_email($_POST['test_email'] ?? '');
1486 if (empty($test_email)) {
1487 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1488 }
1489
1490 // Get payment reminder settings
1491 $subject = get_option('easy_invoice_pro_payment_reminder_subject', __('A friendly reminder - Invoice #{{invoice_number}}', 'easy-invoice-pro'));
1492 $body = get_option('easy_invoice_pro_payment_reminder_body', '');
1493
1494 if (empty($body)) {
1495 wp_send_json_error(['message' => __('Payment reminder email template is empty. Please configure the email message first.', 'easy-invoice')]);
1496 }
1497
1498 // Create sample data for testing
1499 $sample_data = [
1500 'invoice_number' => 'TEST-001',
1501 'client_name' => 'Test Client',
1502 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1503 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1504 'total_amount' => '$1,000.00',
1505 'due_date' => date('Y-m-d', strtotime('+7 days')),
1506 ];
1507
1508 // Replace placeholders
1509 foreach ($sample_data as $placeholder => $value) {
1510 $subject = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $subject);
1511 $body = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $body);
1512 }
1513
1514 // Test email sending
1515 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
1516 $result = $email_manager->sendTestTemplateEmail($test_email, $subject, $body);
1517
1518 if ($result['success']) {
1519 wp_send_json_success($result);
1520 } else {
1521 wp_send_json_error($result);
1522 }
1523 }
1524
1525 /**
1526 * Test template email functionality
1527 */
1528 public function testTemplateEmail(): void {
1529 // Verify nonce
1530 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) {
1531 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
1532 }
1533
1534 // Check permissions
1535 if (!current_user_can('manage_options')) {
1536 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
1537 }
1538
1539 // Get test email address and template type
1540 $test_email = sanitize_email($_POST['test_email'] ?? '');
1541 $template_type = sanitize_text_field($_POST['template_type'] ?? '');
1542
1543 if (empty($test_email)) {
1544 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1545 }
1546
1547 if (empty($template_type)) {
1548 wp_send_json_error(['message' => __('Template type is required', 'easy-invoice')]);
1549 }
1550
1551 // Get the actual configured email settings
1552 $subject = '';
1553 $body = '';
1554
1555 switch ($template_type) {
1556 case 'invoice_available':
1557 $subject = get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1558 $body = get_option('easy_invoice_invoice_email_body', self::getInvoiceEmailBody());
1559 break;
1560 case 'quote_available':
1561 $subject = get_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'));
1562 $body = get_option('easy_invoice_quote_email_body', self::getQuoteEmailBody());
1563 break;
1564 case 'payment_received':
1565 $subject = get_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'));
1566 $body = get_option('easy_invoice_payment_email_body', self::getPaymentEmailBody());
1567 break;
1568 default:
1569 wp_send_json_error(['message' => __('Invalid template type', 'easy-invoice')]);
1570 }
1571
1572 // Ensure we have valid strings
1573 $subject = is_string($subject) ? $subject : '';
1574 $body = is_string($body) ? $body : '';
1575
1576 if (empty($subject) || empty($body)) {
1577 wp_send_json_error(['message' => __('Email template is empty or invalid', 'easy-invoice')]);
1578 }
1579
1580 // Create sample data for testing
1581 $sample_data = [
1582 'invoice_number' => 'TEST-001',
1583 'quote_number' => 'TEST-Q-001',
1584 'client_name' => 'Test Client',
1585 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1586 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1587 'total_amount' => '$1,000.00',
1588 'payment_amount' => '$1,000.00',
1589 'due_date' => date('Y-m-d', strtotime('+30 days')),
1590 'expiry_date' => date('Y-m-d', strtotime('+30 days')),
1591 'payment_date' => date('Y-m-d'),
1592 'payment_method' => 'Credit Card',
1593 'transaction_id' => 'TXN-' . uniqid(),
1594 'payment_terms' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
1595 ];
1596
1597 // Ensure all sample data values are strings
1598 foreach ($sample_data as $key => $value) {
1599 $sample_data[$key] = is_string($value) ? $value : (string) $value;
1600 }
1601
1602 // Replace placeholders in subject and body
1603 foreach ($sample_data as $placeholder => $value) {
1604 $subject = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $subject);
1605 $body = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $body);
1606 }
1607
1608 // Test email sending
1609 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
1610 $result = $email_manager->sendTestTemplateEmail($test_email, $subject, $body);
1611
1612 if ($result['success']) {
1613 wp_send_json_success($result);
1614 } else {
1615 wp_send_json_error($result);
1616 }
1617 }
1618
1619 /**
1620 * Check if quote adjust field should be shown
1621 *
1622 * @return bool True if adjust field should be shown
1623 */
1624 public static function shouldShowQuoteAdjustField(): bool {
1625 return get_option('easy_invoice_quote_show_adjust_field', 'yes') === 'yes';
1626 }
1627
1628 /**
1629 * Check if invoice adjust field should be shown
1630 *
1631 * @return bool True if adjust field should be shown
1632 */
1633 public static function shouldShowInvoiceAdjustField(): bool {
1634 return get_option('easy_invoice_invoice_show_adjust_field', 'yes') === 'yes';
1635 }
1636
1637 /**
1638 * Get invoice prefix
1639 *
1640 * @return string Invoice prefix
1641 */
1642 public static function getInvoicePrefix(): string {
1643 return get_option('easy_invoice_invoice_prefix', 'EIN_');
1644 }
1645
1646 /**
1647 * Get invoice starting number
1648 *
1649 * @return int Invoice starting number
1650 */
1651 public static function getInvoiceStartingNumber(): int {
1652 return (int) get_option('easy_invoice_invoice_starting_number', 0);
1653 }
1654
1655 /**
1656 * Get invoice terms and conditions
1657 *
1658 * @return string Invoice terms and conditions
1659 */
1660 public static function getInvoiceTermsConditions(): string {
1661 return get_option('easy_invoice_invoice_terms_conditions', __('Payment is due within 30 days from date of invoice', 'easy-invoice'));
1662 }
1663
1664 /**
1665 * Get invoice footer text
1666 *
1667 * @return string Invoice footer text
1668 */
1669 public static function getInvoiceFooterText(): string {
1670 return get_option('easy_invoice_invoice_footer_text', '');
1671 }
1672
1673 /**
1674 * Get quote prefix
1675 *
1676 * @return string Quote prefix
1677 */
1678 public static function getQuotePrefix(): string {
1679 return get_option('easy_invoice_quote_prefix', 'EIQN_');
1680 }
1681
1682 /**
1683 * Get quote starting number
1684 *
1685 * @return int Quote starting number
1686 */
1687 public static function getQuoteStartingNumber(): int {
1688 return (int) get_option('easy_invoice_quote_starting_number', 1);
1689 }
1690
1691 /**
1692 * Get quote terms and conditions
1693 *
1694 * @return string Quote terms and conditions
1695 */
1696 public static function getQuoteTermsConditions(): string {
1697 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'));
1698 }
1699
1700 /**
1701 * Get quote footer text
1702 *
1703 * @return string Quote footer text
1704 */
1705 public static function getQuoteFooterText(): string {
1706 return get_option('easy_invoice_quote_footer_text', __('Thanks for choosing Easy Invoice', 'easy-invoice'));
1707 }
1708
1709 /**
1710 * Check if quote accept button should be shown
1711 *
1712 * @return bool True if accept button should be shown
1713 */
1714 public static function shouldShowQuoteAcceptButton(): bool {
1715 return get_option('easy_invoice_quote_accept_button', 'yes') === 'yes';
1716 }
1717
1718 /**
1719 * Get quote accept action
1720 *
1721 * @return string Quote accept action
1722 */
1723 public static function getQuoteAcceptAction(): string {
1724 return get_option('easy_invoice_quote_accept_action', 'convert');
1725 }
1726
1727 /**
1728 * Get quote accept text
1729 *
1730 * @return string Quote accept text
1731 */
1732 public static function getQuoteAcceptText(): string {
1733 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'));
1734 }
1735
1736 /**
1737 * Get accepted quote message
1738 *
1739 * @return string Accepted quote message
1740 */
1741 public static function getAcceptedQuoteMessage(): string {
1742 return get_option('easy_invoice_quote_accepted_message', __('You\'ve confirmed the Quote.<br>We\'ll get in touch with you shortly.', 'easy-invoice'));
1743 }
1744
1745 /**
1746 * Check if decline reason is required
1747 *
1748 * @return bool True if decline reason is required
1749 */
1750 public static function isDeclineReasonRequired(): bool {
1751 return get_option('easy_invoice_quote_decline_reason_required', 'no') === 'yes';
1752 }
1753
1754 /**
1755 * Get declined quote message
1756 *
1757 * @return string Declined quote message
1758 */
1759 public static function getDeclinedQuoteMessage(): string {
1760 return get_option('easy_invoice_quote_declined_message', '');
1761 }
1762
1763 // Email Settings Helper Functions
1764
1765 /**
1766 * Check if invoice available email is enabled
1767 *
1768 * @return bool True if invoice email is enabled
1769 */
1770 public static function isInvoiceEmailEnabled(): bool {
1771 return get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes';
1772 }
1773
1774 /**
1775 * Get invoice email subject
1776 *
1777 * @return string Invoice email subject
1778 */
1779 public static function getInvoiceEmailSubject(): string {
1780 return get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1781 }
1782
1783 /**
1784 * Get invoice email body
1785 *
1786 * @return string Invoice email body
1787 */
1788 public static function getInvoiceEmailBody(): string {
1789 return get_option('easy_invoice_invoice_email_body', __('<h2>📄 Your Invoice is Ready</h2>
1790
1791 <p>Dear {{client_name}},</p>
1792
1793 <div class="highlight-box">
1794 <p><strong>Invoice #{{invoice_number}}</strong><br>
1795 <span class="amount-highlight">{{total_amount}}</span><br>
1796 Due Date: <strong>{{due_date}}</strong></p>
1797 </div>
1798
1799 <p>Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment.</p>
1800
1801 <div class="info-box">
1802 <p><strong>📋 Payment Details:</strong><br>
1803 • Invoice Number: {{invoice_number}}<br>
1804 • Total Amount: {{total_amount}}<br>
1805 • Due Date: {{due_date}}<br>
1806 • Payment Terms: {{payment_terms}}</p>
1807 </div>
1808
1809 <div class="warning-box">
1810 <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1811 </div>
1812
1813 <p>If you have any questions about this invoice, please do not hesitate to contact us.</p>
1814
1815 <div class="divider"></div>
1816
1817 <p>Thank you for your business!</p>
1818
1819 <p>Best regards,<br>
1820 <strong>{{company_name}}</strong><br>
1821 {{company_email}}</p>', 'easy-invoice'));
1822 }
1823
1824 /**
1825 * Check if quote available email is enabled
1826 *
1827 * @return bool True if quote email is enabled
1828 */
1829 public static function isQuoteEmailEnabled(): bool {
1830 return get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes';
1831 }
1832
1833 /**
1834 * Get quote email subject
1835 *
1836 * @return string Quote email subject
1837 */
1838 public static function getQuoteEmailSubject(): string {
1839 return get_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'));
1840 }
1841
1842 /**
1843 * Get quote email body
1844 *
1845 * @return string Quote email body
1846 */
1847 public static function getQuoteEmailBody(): string {
1848 return get_option('easy_invoice_quote_email_body', __('<h2>📋 Your Quote is Ready</h2>
1849
1850 <p>Dear {{client_name}},</p>
1851
1852 <div class="highlight-box">
1853 <p><strong>Quote #{{quote_number}}</strong><br>
1854 <span class="amount-highlight">{{total_amount}}</span><br>
1855 Valid Until: <strong>{{expiry_date}}</strong></p>
1856 </div>
1857
1858 <p>We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment.</p>
1859
1860 <div class="info-box">
1861 <p><strong>📋 Quote Summary:</strong><br>
1862 • Quote Number: {{quote_number}}<br>
1863 • Total Amount: {{total_amount}}<br>
1864 • Valid Until: {{expiry_date}}<br>
1865 • Terms: {{payment_terms}}</p>
1866 </div>
1867
1868 <div class="warning-box">
1869 <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
1870 </div>
1871
1872 <p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
1873
1874 <div class="divider"></div>
1875
1876 <p>We look forward to working with you!</p>
1877
1878 <p>Best regards,<br>
1879 <strong>{{company_name}}</strong><br>
1880 {{company_email}}</p>', 'easy-invoice'));
1881 }
1882
1883 /**
1884 * Check if payment received email is enabled
1885 *
1886 * @return bool True if payment email is enabled
1887 */
1888 public static function isPaymentEmailEnabled(): bool {
1889 return get_option('easy_invoice_payment_email_enabled', 'yes') === 'yes';
1890 }
1891
1892 /**
1893 * Get payment email subject
1894 *
1895 * @return string Payment email subject
1896 */
1897 public static function getPaymentEmailSubject(): string {
1898 return get_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'));
1899 }
1900
1901 /**
1902 * Get payment email body
1903 *
1904 * @return string Payment email body
1905 */
1906 public static function getPaymentEmailBody(): string {
1907 return get_option('easy_invoice_payment_email_body', __('<h2>�
1908 Payment Received - Thank You!</h2>
1909
1910 <p>Dear {{client_name}},</p>
1911
1912 <div class="success-box">
1913 <p><strong>Payment Confirmation</strong><br>
1914 Invoice #{{invoice_number}}<br>
1915 <span class="amount-highlight">{{payment_amount}}</span><br>
1916 Payment Date: <strong>{{payment_date}}</strong><br>
1917 Payment Method: <strong>{{payment_method}}</strong></p>
1918 </div>
1919
1920 <p>We have successfully received your payment. Thank you for your prompt payment!</p>
1921
1922 <div class="info-box">
1923 <p><strong>📊 Payment Details:</strong><br>
1924 • Invoice Number: {{invoice_number}}<br>
1925 • Amount Paid: {{payment_amount}}<br>
1926 • Payment Date: {{payment_date}}<br>
1927 • Payment Method: {{payment_method}}<br>
1928 • Transaction ID: {{transaction_id}}</p>
1929 </div>
1930
1931 <div class="highlight-box">
1932 <p><strong>🎉 Status: PAID</strong><br>
1933 Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1934 </div>
1935
1936 <p>If you have any questions about this payment or need a receipt, please do not hesitate to contact us.</p>
1937
1938 <div class="divider"></div>
1939
1940 <p>Thank you for choosing our services!</p>
1941
1942 <p>Best regards,<br>
1943 <strong>{{company_name}}</strong><br>
1944 {{company_email}}</p>', 'easy-invoice'));
1945 }
1946
1947 // ========================================
1948 // TEXT SETTINGS HELPER FUNCTIONS
1949 // ========================================
1950 //
1951 // USAGE IN TEMPLATES:
1952 // Instead of: echo __('Invoice', 'easy-invoice');
1953 // Use: echo \EasyInvoice\Controllers\SettingsController::getTextInvoice();
1954 //
1955 // Example:
1956 // <h1>echo \EasyInvoice\Controllers\SettingsController::getTextInvoice();</h1>
1957 // <p>echo \EasyInvoice\Controllers\SettingsController::getTextFrom(); : echo $company_name;</p>
1958 // <p>echo \EasyInvoice\Controllers\SettingsController::getTextTo(); : echo $client_name;</p>
1959 //
1960
1961 /**
1962 * Get custom text setting with fallback to default
1963 *
1964 * @param string $key Text setting key
1965 * @param string $default Default text
1966 * @return string Custom text or default
1967 */
1968 public static function getTextSetting(string $key, string $default): string {
1969 $option_key = 'easy_invoice_text_' . $key;
1970 return get_option($option_key, $default);
1971 }
1972
1973 // Invoice Text Settings
1974 public static function getTextInvoice(): string {
1975 return self::getTextSetting('invoice', __('Invoice', 'easy-invoice'));
1976 }
1977
1978 public static function getTextInvoices(): string {
1979 return self::getTextSetting('invoices', __('Invoices', 'easy-invoice'));
1980 }
1981
1982 public static function getTextTo(): string {
1983 return self::getTextSetting('to', __('To', 'easy-invoice'));
1984 }
1985
1986 public static function getTextInvoiceNumber(): string {
1987 return self::getTextSetting('invoice_number', __('Invoice Number', 'easy-invoice'));
1988 }
1989
1990 public static function getTextInvoiceDate(): string {
1991 return self::getTextSetting('invoice_date', __('Invoice Date', 'easy-invoice'));
1992 }
1993
1994 public static function getTextDueDate(): string {
1995 return self::getTextSetting('due_date', __('Due Date', 'easy-invoice'));
1996 }
1997
1998 public static function getTextTotalDue(): string {
1999 return self::getTextSetting('total_due', __('Total Due', 'easy-invoice'));
2000 }
2001
2002 public static function getTextQty(): string {
2003 return self::getTextSetting('qty', __('Qty', 'easy-invoice'));
2004 }
2005
2006 public static function getTextService(): string {
2007 return self::getTextSetting('service', __('Service', 'easy-invoice'));
2008 }
2009
2010 public static function getTextRatePrice(): string {
2011 return self::getTextSetting('rate_price', __('Rate', 'easy-invoice'));
2012 }
2013
2014 public static function getTextAdjust(): string {
2015 return self::getTextSetting('adjust', __('Adjust', 'easy-invoice'));
2016 }
2017
2018 public static function getTextSubTotal(): string {
2019 return self::getTextSetting('sub_total', __('Sub Total', 'easy-invoice'));
2020 }
2021
2022 public static function getTextTotal(): string {
2023 return self::getTextSetting('total', __('Total', 'easy-invoice'));
2024 }
2025
2026 public static function getTextTax(): string {
2027 return self::getTextSetting('tax', __('Tax', 'easy-invoice'));
2028 }
2029
2030 public static function getTextDiscount(): string {
2031 return self::getTextSetting('discount', __('Discount', 'easy-invoice'));
2032 }
2033
2034 public static function getTextPrint(): string {
2035 return self::getTextSetting('print', __('Print', 'easy-invoice'));
2036 }
2037
2038 public static function getTextDownloadPdf(): string {
2039 return self::getTextSetting('download_pdf', __('Download as PDF', 'easy-invoice'));
2040 }
2041
2042 public static function getTextSendEmail(): string {
2043 return self::getTextSetting('send_email', __('Send Email', 'easy-invoice'));
2044 }
2045
2046 public static function getTextPayNow(): string {
2047 return self::getTextSetting('pay_now', __('Pay Now', 'easy-invoice'));
2048 }
2049
2050 // Quote Text Settings
2051 public static function getTextQuote(): string {
2052 return self::getTextSetting('quote', __('Quote', 'easy-invoice'));
2053 }
2054
2055 public static function getTextQuoteNumber(): string {
2056 return self::getTextSetting('quote_number', __('Quote Number', 'easy-invoice'));
2057 }
2058
2059 public static function getTextAcceptQuote(): string {
2060 return self::getTextSetting('accept_quote', __('Accept Quote', 'easy-invoice'));
2061 }
2062
2063 public static function getTextDeclineQuote(): string {
2064 return self::getTextSetting('decline_quote', __('Decline Quote', 'easy-invoice'));
2065 }
2066
2067 public static function getTextDeclineReason(): string {
2068 return self::getTextSetting('decline_reason', __('Reason for declining', 'easy-invoice'));
2069 }
2070
2071 public static function getTextValidUntil(): string {
2072 return self::getTextSetting('valid_until', __('Valid Until Date', 'easy-invoice'));
2073 }
2074
2075 public static function getTextQuoteDate(): string {
2076 return self::getTextSetting('quote_date', __('Quote Date', 'easy-invoice'));
2077 }
2078
2079 public static function getTextFrom(): string {
2080 return self::getTextSetting('from', __('From', 'easy-invoice'));
2081 }
2082
2083 /**
2084 * Get the actual date format string from the stored format identifier
2085 *
2086 * @param string $format_identifier The format identifier (us, uk, iso)
2087 * @return string The actual date format string
2088 */
2089 public static function getDateFormatString($format_identifier = null): string {
2090 if ($format_identifier === null) {
2091 $format_identifier = get_option('easy_invoice_date_format', 'us');
2092 }
2093
2094 switch ($format_identifier) {
2095 case 'uk':
2096 return 'd/m/Y';
2097 case 'iso':
2098 return 'Y-m-d';
2099 case 'us':
2100 default:
2101 return 'm/d/Y';
2102 }
2103 }
2104
2105 /**
2106 * Get the current date format string
2107 *
2108 * @return string The current date format string
2109 */
2110 public static function getCurrentDateFormat(): string {
2111 return self::getDateFormatString();
2112 }
2113
2114 /**
2115 * Format a date using the Easy Invoice date format setting
2116 *
2117 * @param string|int $date The date to format (timestamp or date string)
2118 * @return string The formatted date
2119 */
2120 public static function formatDate($date): string {
2121 $format = self::getCurrentDateFormat();
2122 $timestamp = is_numeric($date) ? $date : strtotime($date);
2123 return date_i18n($format, $timestamp);
2124 }
2125
2126 /**
2127 * AJAX handler for regenerating invoice numbers
2128 */
2129 public function ajaxRegenerateInvoiceNumbers() {
2130
2131 // Verify nonce
2132 $nonce = $_POST['nonce'] ?? $_POST['easy_invoice_settings_nonce'] ?? '';
2133 if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) {
2134 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
2135 }
2136
2137 // Check permissions
2138 if (!current_user_can('manage_options')) {
2139 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
2140 }
2141
2142 try {
2143 // Get the current next invoice number
2144 $current_next_number = intval(get_option('easy_invoice_next_invoice_number', 1));
2145 $prefix = get_option('easy_invoice_invoice_prefix', 'EIIN_');
2146
2147 // Start regeneration from the current next number
2148 $starting_number = $current_next_number;
2149
2150 // Get all invoices ordered by creation date
2151 $invoices = get_posts([
2152 'post_type' => 'easy_invoice',
2153 'post_status' => 'publish',
2154 'numberposts' => -1,
2155 'orderby' => 'date',
2156 'order' => 'ASC'
2157 ]);
2158
2159 if (empty($invoices)) {
2160 wp_send_json_success([
2161 'message' => __('No invoices found to regenerate', 'easy-invoice'),
2162 'regenerated_count' => 0
2163 ]);
2164 }
2165
2166 $regenerated_count = 0;
2167 $current_number = $starting_number;
2168
2169 foreach ($invoices as $invoice) {
2170 // Generate new invoice number
2171 $new_invoice_number = $prefix . str_pad($current_number, 6, '0', STR_PAD_LEFT);
2172
2173 // Update the invoice number
2174 update_post_meta($invoice->ID, '_easy_invoice_number', $new_invoice_number);
2175
2176 $regenerated_count++;
2177 $current_number++;
2178 }
2179
2180 // Update the next invoice number counter to continue from the last regenerated number + 1
2181 update_option('easy_invoice_next_invoice_number', $current_number);
2182
2183 wp_send_json_success([
2184 'message' => sprintf(__('Successfully regenerated %d invoice numbers', 'easy-invoice'), $regenerated_count),
2185 'regenerated_count' => $regenerated_count,
2186 'next_number' => $current_number
2187 ]);
2188
2189 } catch (Exception $e) {
2190 wp_send_json_error(['message' => __('Failed to regenerate invoice numbers', 'easy-invoice')]);
2191 }
2192 }
2193
2194 /**
2195 * AJAX handler for regenerating quote numbers
2196 */
2197 public function ajaxRegenerateQuoteNumbers() {
2198
2199 // Verify nonce
2200 $nonce = $_POST['nonce'] ?? $_POST['easy_invoice_settings_nonce'] ?? '';
2201 if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) {
2202 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
2203 }
2204
2205 // Check permissions
2206 if (!current_user_can('manage_options')) {
2207 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
2208 }
2209
2210 try {
2211 $current_next_number = intval(get_option('easy_invoice_next_quote_number', 1));
2212 $prefix = get_option('easy_invoice_quote_prefix', 'QT-');
2213
2214 $starting_number = $current_next_number;
2215
2216 $quotes = get_posts([
2217 'post_type' => 'easy_invoice_quote',
2218 'post_status' => 'publish',
2219 'numberposts' => -1,
2220 'orderby' => 'date',
2221 'order' => 'ASC',
2222 'meta_query' => [
2223 [
2224 'key' => '_easy_invoice_quote_number',
2225 'compare' => 'EXISTS'
2226 ]
2227 ]
2228 ]);
2229
2230 $regenerated_count = 0;
2231 $current_number = $starting_number;
2232
2233 foreach ($quotes as $quote_post) {
2234 $new_quote_number = $prefix . str_pad($current_number, 6, '0', STR_PAD_LEFT);
2235 update_post_meta($quote_post->ID, '_easy_invoice_quote_number', $new_quote_number);
2236 $regenerated_count++;
2237 $current_number++;
2238 }
2239
2240 update_option('easy_invoice_next_quote_number', $current_number);
2241
2242 wp_send_json_success([
2243 'message' => sprintf(__('Successfully regenerated %d quote numbers', 'easy-invoice'), $regenerated_count),
2244 'regenerated_count' => $regenerated_count,
2245 'next_number' => $current_number
2246 ]);
2247
2248 } catch (Exception $e) {
2249 wp_send_json_error(['message' => __('Failed to regenerate quote numbers', 'easy-invoice')]);
2250 }
2251 }
2252 }
2253