PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.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 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / includes / Controllers / SettingsController.php

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

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