PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
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
← All changes | includes/Controllers/SettingsController.php +438 -563 2.1.22.4.0 View file →
@@ -23,10 +23,27 @@
23 23 *
24 24 * @var array
25 25 */
26 26 private $settings_cache = null;
27 -
27 +
28 28 /**
29 + * Settings resolved once per request. Every money formatter used to
30 + * construct its own controller and rebuild the whole settings array (the
31 + * config tree with every translated label) — around a millisecond per
32 + * formatted amount, ten seconds on a 4,500-line statement.
33 + *
34 + * @var array|null
35 + */
36 + private static $shared_settings_cache = null;
37 +
38 + /**
39 + * Drop the per-request settings cache (any easy_invoice_* option changed).
40 + */
41 + public static function flushSharedCache(): void {
42 + self::$shared_settings_cache = null;
43 + }
44 +
45 + /**
29 46 * Settings configuration cache
30 47 *
31 48 * @var array
32 49 */
@@ -59,21 +76,21 @@
59 76 add_action('wp_ajax_easy_invoice_test_template_email', [$this, 'testTemplateEmail']);
60 77 add_action('wp_ajax_easy_invoice_test_payment_reminder_email', [$this, 'testPaymentReminderEmail']);
61 78 add_action('wp_ajax_regenerate_invoice_numbers', [$this, 'ajaxRegenerateInvoiceNumbers']);
62 79 add_action('wp_ajax_regenerate_quote_numbers', [$this, 'ajaxRegenerateQuoteNumbers']);
63 -
80 +
64 81 // Clear cache on admin init to ensure new fields are recognized
65 82 add_action('admin_init', [$this, 'clearCache']);
66 -
83 +
67 84 // Enqueue admin scripts
68 85 add_action('admin_enqueue_scripts', [$this, 'enqueueAdminScripts']);
69 -
86 +
70 87 // Initialize settings
71 88 $this->initializeSettings();
72 89 }
73 -
74 -
75 -
90 +
91 +
92 +
76 93 /**
77 94 * Enqueue admin scripts and styles for the settings page.
78 95 *
79 96 * @param string $hook_suffix The current admin page.
@@ -83,9 +100,9 @@
83 100 // Check if we are on the Easy Invoice settings page.
84 101 if (empty($hook_suffix) || strpos($hook_suffix, PagesSlugs::SETTINGS) === false) {
85 102 return;
86 103 }
87 -
104 +
88 105 wp_enqueue_script('jquery-ui-sortable');
89 106 wp_enqueue_media(); // For the logo uploader
90 107
91 108 // Enqueue Select2 for enhancing multiselect fields
@@ -90,21 +107,22 @@
90 107
91 108 // Enqueue Select2 for enhancing multiselect fields
92 109 wp_enqueue_style(
93 110 'select2',
94 - 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css',
111 + EASY_INVOICE_URL . 'assets/lib/select2/select2.min.css',
95 112 [],
96 113 '4.0.13'
97 114 );
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 115
116 +
117 + wp_enqueue_script(
118 + 'select2',
119 + EASY_INVOICE_URL . 'assets/lib/select2/select2.min.js',
120 + ['jquery'],
121 + '4.0.13',
122 + true
123 + );
124 +
107 125 // Enqueue confirmation modal for premium features
108 126 wp_enqueue_script(
109 127 'easy-invoice-confirmation-modal',
110 128 EASY_INVOICE_URL . 'assets/js/confirmation-modal.js',
@@ -123,12 +141,12 @@
123 141 );
124 142
125 143 $script_handle = 'easy-invoice-settings';
126 144 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,
145 + $script_handle,
146 + EASY_INVOICE_URL . 'assets/js/settings.js',
147 + ['jquery', 'jquery-ui-sortable', 'wp-util', 'select2', 'easy-invoice-confirmation-modal', 'media-views'],
148 + EASY_INVOICE_VERSION,
131 149 true
132 150 );
133 151
134 152 // Enqueue the new settings.css only on the settings page
@@ -144,11 +162,11 @@
144 162 'nonce' => wp_create_nonce('easy_invoice_settings'),
145 163 'saveSuccess' => __('Settings saved successfully', 'easy-invoice'),
146 164 'saveError' => __('Error saving settings', 'easy-invoice'),
147 165 ]);
148 -
166 +
149 167 wp_localize_script($script_handle, 'easyInvoiceSettings', $localize_data);
150 -
168 +
151 169 do_action('easy_invoice_after_admin_scripts', $hook_suffix);
152 170 }
153 171
154 172 /**
@@ -169,9 +187,9 @@
169 187 // Use cached config if available
170 188 if (is_array($this->config_cache)) {
171 189 return $this->config_cache;
172 190 }
173 -
191 +
174 192 $config = [
175 193 'company' => [
176 194 'title' => __('Company Information', 'easy-invoice'),
177 195 'description' => __('Set up your company details', 'easy-invoice'),
@@ -180,8 +198,12 @@
180 198 'easy_invoice_company_name' => ['label' => __('Company Name', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6' ],
181 199 'easy_invoice_company_email' => ['label' => __('Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-6' ],
182 200 'easy_invoice_company_phone' => ['label' => __('Phone Number', 'easy-invoice'), 'type' => 'tel', 'default' => '', 'col_span' => 'sm:col-span-6' ],
183 201 'easy_invoice_company_website' => ['label' => __('Website', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6' ],
202 + // Both are required by EN 16931 and are what makes an intra-EU
203 + // reverse charge determinable. See Services\TaxTreatment.
204 + 'easy_invoice_company_vat_number' => ['label' => __('VAT number', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3', 'description' => __('Your VAT identifier, including the country prefix — for example DE123456789.', 'easy-invoice') ],
205 + 'easy_invoice_company_country' => ['label' => __('Country', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3', 'description' => __('Two-letter country code, for example DE, FR or GB.', 'easy-invoice') ],
184 206 'easy_invoice_company_address' => ['label' => __('Address', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6' ],
185 207 'easy_invoice_tax_number' => ['label' => __('Tax ID / VAT Number', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6' ],
186 208 'easy_invoice_company_logo' => ['label' => __('Company Logo', 'easy-invoice'), 'type' => 'image', 'default' => '', 'col_span' => 'sm:col-span-6' ],
187 209 ]
@@ -191,24 +213,24 @@
191 213 'description' => __('Configure invoice-specific settings and preferences', 'easy-invoice'),
192 214 'icon' => 'fas fa-file-invoice',
193 215 'fields' => [
194 216 'easy_invoice_invoice_prefix' => [
195 - 'label' => __('Invoice Prefix', 'easy-invoice'),
196 - 'type' => 'text',
197 - 'default' => 'EIIN_',
217 + 'label' => __('Invoice Prefix', 'easy-invoice'),
218 + 'type' => 'text',
219 + 'default' => 'EIIN_',
198 220 'description' => __('Prefix for invoice numbers (e.g., EIIN_, INV-)', 'easy-invoice'),
199 221 'col_span' => 'sm:col-span-2'
200 222 ],
201 223 'easy_invoice_next_invoice_number' => [
202 - 'label' => __('Next Invoice Number', 'easy-invoice'),
203 - 'type' => 'number',
204 - 'default' => '1',
224 + 'label' => __('Next Invoice Number', 'easy-invoice'),
225 + 'type' => 'number',
226 + 'default' => '1',
205 227 'description' => __('Set the next invoice number to be generated. This will be the number of the next invoice created.', 'easy-invoice'),
206 228 'col_span' => 'sm:col-span-2',
207 229 ],
208 230 'easy_invoice_regenerate_invoice_numbers' => [
209 - 'label' => __('Regenerate Invoice Numbers', 'easy-invoice'),
210 - 'type' => 'button',
231 + 'label' => __('Regenerate Invoice Numbers', 'easy-invoice'),
232 + 'type' => 'button',
211 233 'button_text' => __('Regenerate All Invoice Numbers', 'easy-invoice'),
212 234 'button_class' => 'button button-secondary',
213 235 '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 236 'col_span' => 'sm:col-span-2',
@@ -214,25 +236,25 @@
214 236 'col_span' => 'sm:col-span-2',
215 237 'ajax_action' => 'regenerate_invoice_numbers'
216 238 ],
217 239 'easy_invoice_invoice_show_adjust_field' => [
218 - 'label' => __('Show/Hide Adjust Field', 'easy-invoice'),
219 - 'type' => 'checkbox',
220 - 'default' => 'yes',
240 + 'label' => __('Show/Hide Adjust Field', 'easy-invoice'),
241 + 'type' => 'checkbox',
242 + 'default' => 'yes',
221 243 'description' => __('Enable/Disable Adjust field. Tick this to show adjust field', 'easy-invoice'),
222 244 'col_span' => 'sm:col-span-6'
223 245 ],
224 246 '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'),
247 + 'label' => __('Terms & Conditions', 'easy-invoice'),
248 + 'type' => 'wp_editor',
249 + 'default' => __('Payment is due within 30 days from date of invoice', 'easy-invoice'),
228 250 'description' => __('Terms and conditions that will be displayed on your invoice', 'easy-invoice'),
229 251 'col_span' => 'sm:col-span-6'
230 252 ],
231 253 'easy_invoice_invoice_footer_text' => [
232 - 'label' => __('Footer Text', 'easy-invoice'),
233 - 'type' => 'wp_editor',
234 - 'default' => '',
254 + 'label' => __('Footer Text', 'easy-invoice'),
255 + 'type' => 'wp_editor',
256 + 'default' => '',
235 257 '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 258 'col_span' => 'sm:col-span-6'
237 259 ],
238 260 ],
@@ -242,24 +264,24 @@
242 264 'description' => __('Configure quote-specific settings and preferences', 'easy-invoice'),
243 265 'icon' => 'fas fa-file-contract',
244 266 'fields' => [
245 267 'easy_invoice_quote_prefix' => [
246 - 'label' => __('Quote Prefix', 'easy-invoice'),
247 - 'type' => 'text',
248 - 'default' => 'EIQN_',
268 + 'label' => __('Quote Prefix', 'easy-invoice'),
269 + 'type' => 'text',
270 + 'default' => 'EIQN_',
249 271 'description' => __('Prefix for quote numbers (e.g., EIQN_, QT-)', 'easy-invoice'),
250 272 'col_span' => 'sm:col-span-2'
251 273 ],
252 274 'easy_invoice_next_quote_number' => [
253 - 'label' => __('Next Quote Number', 'easy-invoice'),
254 - 'type' => 'number',
255 - 'default' => '1',
275 + 'label' => __('Next Quote Number', 'easy-invoice'),
276 + 'type' => 'number',
277 + 'default' => '1',
256 278 'description' => __('Set the next quote number to be generated. This will be the number of the next quote created.', 'easy-invoice'),
257 279 'col_span' => 'sm:col-span-2',
258 280 ],
259 281 'easy_invoice_regenerate_quote_numbers' => [
260 - 'label' => __('Regenerate Quote Numbers', 'easy-invoice'),
261 - 'type' => 'button',
282 + 'label' => __('Regenerate Quote Numbers', 'easy-invoice'),
283 + 'type' => 'button',
262 284 'button_text' => __('Regenerate All Quote Numbers', 'easy-invoice'),
263 285 'button_class' => 'button button-secondary',
264 286 '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 287 'col_span' => 'sm:col-span-2',
@@ -265,38 +287,38 @@
265 287 'col_span' => 'sm:col-span-2',
266 288 'ajax_action' => 'regenerate_quote_numbers'
267 289 ],
268 290 'easy_invoice_quote_show_adjust_field' => [
269 - 'label' => __('Show/Hide Adjust Field', 'easy-invoice'),
270 - 'type' => 'checkbox',
271 - 'default' => 'yes',
291 + 'label' => __('Show/Hide Adjust Field', 'easy-invoice'),
292 + 'type' => 'checkbox',
293 + 'default' => 'yes',
272 294 'description' => __('Enable/Disable Adjust field. Tick this to show adjust field', 'easy-invoice'),
273 295 'col_span' => 'sm:col-span-6'
274 296 ],
275 297 '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'),
298 + 'label' => __('Terms & Conditions', 'easy-invoice'),
299 + 'type' => 'wp_editor',
300 + 'default' => __('This quote has a fixed price. Upon acceptance, we kindly ask for a 25% deposit prior to initiating the work.', 'easy-invoice'),
279 301 'description' => __('Terms and conditions that will be displayed on your quote!', 'easy-invoice'),
280 302 'col_span' => 'sm:col-span-6'
281 303 ],
282 304 'easy_invoice_quote_footer_text' => [
283 - 'label' => __('Footer Text', 'easy-invoice'),
284 - 'type' => 'wp_editor',
285 - 'default' => __('Thanks for choosing Easy Invoice', 'easy-invoice'),
305 + 'label' => __('Footer Text', 'easy-invoice'),
306 + 'type' => 'wp_editor',
307 + 'default' => __('Thank you for your business.', 'easy-invoice'),
286 308 '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 309 'col_span' => 'sm:col-span-6'
288 310 ],
289 311 'easy_invoice_quote_accept_button' => [
290 - 'label' => __('Accept quote button', 'easy-invoice'),
291 - 'type' => 'checkbox',
292 - 'default' => 'yes',
312 + 'label' => __('Accept quote button', 'easy-invoice'),
313 + 'type' => 'checkbox',
314 + 'default' => 'yes',
293 315 'description' => __('Show/hide accept quote button on quotes.', 'easy-invoice'),
294 316 'col_span' => 'sm:col-span-6'
295 317 ],
296 318 'easy_invoice_quote_accept_action' => [
297 - 'label' => __('Accept quote button action', 'easy-invoice'),
298 - 'type' => 'select',
319 + 'label' => __('Accept quote button action', 'easy-invoice'),
320 + 'type' => 'select',
299 321 'default' => 'convert',
300 322 'options' => [
301 323 'convert' => __('Convert quote to invoice - Draft', 'easy-invoice'),
302 324 'convert_available' => __('Convert quote to invoice - Available', 'easy-invoice'),
@@ -308,32 +330,32 @@
308 330 'description' => __('Upon the client clicking the "accept quote" button, the subsequent action will be activated.', 'easy-invoice'),
309 331 'col_span' => 'sm:col-span-6'
310 332 ],
311 333 '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'),
334 + 'label' => __('Accept Quote Text', 'easy-invoice'),
335 + 'type' => 'wp_editor',
336 + 'default' => __('Important: When you accept this Quote, an Invoice will be created automatically. This will form a legally binding contract.', 'easy-invoice'),
315 337 'description' => __('This information tells your client what happens once they accept the Quote', 'easy-invoice'),
316 338 'col_span' => 'sm:col-span-6'
317 339 ],
318 340 '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'),
341 + 'label' => __('Accepted Quote Message', 'easy-invoice'),
342 + 'type' => 'wp_editor',
343 + 'default' => __('You\'ve confirmed the Quote.<br>We\'ll get in touch with you shortly.', 'easy-invoice'),
322 344 'description' => __('If the client accepts the Quote, display this message.', 'easy-invoice'),
323 345 'col_span' => 'sm:col-span-6'
324 346 ],
325 347 'easy_invoice_quote_decline_reason_required' => [
326 - 'label' => __('Decline Reason Required', 'easy-invoice'),
327 - 'type' => 'checkbox',
328 - 'default' => 'no',
348 + 'label' => __('Decline Reason Required', 'easy-invoice'),
349 + 'type' => 'checkbox',
350 + 'default' => 'no',
329 351 'description' => __('Make the \'Reason for declining\' field mandatory when rejecting.', 'easy-invoice'),
330 352 'col_span' => 'sm:col-span-6'
331 353 ],
332 354 'easy_invoice_quote_declined_message' => [
333 - 'label' => __('Declined Quote Message', 'easy-invoice'),
334 - 'type' => 'wp_editor',
335 - 'default' => '',
355 + 'label' => __('Declined Quote Message', 'easy-invoice'),
356 + 'type' => 'wp_editor',
357 + 'default' => '',
336 358 'description' => __('Message to display if client declines the Quote', 'easy-invoice'),
337 359 'col_span' => 'sm:col-span-6'
338 360 ],
339 361 ],
@@ -343,10 +365,10 @@
343 365 'description' => __('Configure currency display preferences', 'easy-invoice'),
344 366 'icon' => 'fas fa-dollar-sign',
345 367 'fields' => [
346 368 'easy_invoice_currency_code' => [
347 - 'label' => __('Currency & Symbol', 'easy-invoice'),
348 - 'type' => 'select',
369 + 'label' => __('Currency & Symbol', 'easy-invoice'),
370 + 'type' => 'select',
349 371 'default' => 'USD',
350 372 'options' => CurrencyHelper::getCurrencyOptions(),
351 373 'col_span' => 'sm:col-span-6',
352 374 'description' => __('Select your preferred currency. The symbol will be automatically set.', 'easy-invoice')
@@ -351,10 +373,10 @@
351 373 'col_span' => 'sm:col-span-6',
352 374 'description' => __('Select your preferred currency. The symbol will be automatically set.', 'easy-invoice')
353 375 ],
354 376 'easy_invoice_currency_position' => [
355 - 'label' => __('Symbol Position', 'easy-invoice'),
356 - 'type' => 'select',
377 + 'label' => __('Symbol Position', 'easy-invoice'),
378 + 'type' => 'select',
357 379 'default' => 'left',
358 380 'options' => [
359 381 'left' => __('Left', 'easy-invoice'),
360 382 'right' => __('Right', 'easy-invoice'),
@@ -363,10 +385,10 @@
363 385 ],
364 386 'col_span' => 'sm:col-span-3'
365 387 ],
366 388 'easy_invoice_currency_symbol_type' => [
367 - 'label' => __('Currency Symbol Type', 'easy-invoice'),
368 - 'type' => 'select',
389 + 'label' => __('Currency Symbol Type', 'easy-invoice'),
390 + 'type' => 'select',
369 391 'default' => 'symbol',
370 392 'options' => [
371 393 'code' => __('Currency Code', 'easy-invoice'),
372 394 'symbol' => __('Currency Symbol', 'easy-invoice'),
@@ -385,10 +407,10 @@
385 407 'icon' => 'fas fa-percent',
386 408 'fields' => [
387 409 'easy_invoice_tax_enabled' => ['label' => __('Enable Tax', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6' ],
388 410 'easy_invoice_tax_entry_method' => [
389 - 'label' => __('How do you enter tax?', 'easy-invoice'),
390 - 'type' => 'select',
411 + 'label' => __('How do you enter tax?', 'easy-invoice'),
412 + 'type' => 'select',
391 413 'default' => 'exclusive',
392 414 'options' => [
393 415 'inclusive' => __('I will enter price inclusive of tax', 'easy-invoice'),
394 416 'exclusive' => __('I will enter price exclusive of tax', 'easy-invoice'),
@@ -421,15 +443,22 @@
421 443 'easy_invoice_email_reply_to' => ['label' => __('Reply-To Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-3'],
422 444 'easy_invoice_email_reply_to_name' => ['label' => __('Reply-To Name', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3'],
423 445 'easy_invoice_enable_email_styling' => ['label' => __('Enable HTML Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
424 446 'easy_invoice_bcc_admin' => ['label' => __('BCC Admin on All Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6'],
447 + // Off by default: attaching a document changes what every
448 + // customer receives and makes messages much larger, which
449 + // some SMTP relays reject. See EmailManager::shouldAttachInvoicePdf().
450 + 'easy_invoice_attach_pdf_to_email' => ['label' => __('Attach a PDF copy to invoice and quote emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6', 'description' => __('Generates the document on the server and attaches it. The PDF contains real, selectable text rather than an image.', 'easy-invoice')],
451 + 'easy_invoice_pdf_paper_size' => ['label' => __('PDF paper size', 'easy-invoice'), 'type' => 'select', 'default' => 'a4', 'options' => ['a4' => 'A4', 'letter' => 'Letter', 'legal' => 'Legal'], 'col_span' => 'sm:col-span-3'],
452 + 'easy_invoice_pdf_download_method' => ['label' => __('"Download as PDF" button', 'easy-invoice'), 'type' => 'select', 'default' => 'browser', 'options' => ['browser' => __('Exactly as shown on screen (rendered in the browser)', 'easy-invoice'), 'server' => __('Generated on the server (real text, smaller file)', 'easy-invoice')], 'col_span' => 'sm:col-span-3', 'description' => __('The browser capture reproduces the page pixel for pixel — every design, custom template and watermark. The server file is selectable text and a fraction of the size. Emails, the API and e-invoicing always use the server.', 'easy-invoice')],
453 + 'easy_invoice_pdf_layout' => ['label' => __('Server-generated PDF layout', 'easy-invoice'), 'type' => 'select', 'default' => 'design', 'options' => ['design' => __('Match the selected invoice / quote design', 'easy-invoice'), 'plain' => __('Plain print layout', 'easy-invoice')], 'col_span' => 'sm:col-span-3', 'description' => __('Applies to PDFs the server produces: email attachments, the API, e-invoicing, and the download button when set to "server". The design is the template chosen on the document; the plain layout is a neutral table-based page.', 'easy-invoice')],
425 454 '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 455 'easy_invoice_email_logo' => ['label' => __('Email Logo URL', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6'],
427 456 'easy_invoice_email_footer_text' => ['label' => __('Email Footer Text', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6'],
428 457 'easy_invoice_test_email' => [
429 - 'label' => __('Test Email', 'easy-invoice'),
430 - 'type' => 'test_email',
431 - 'default' => '',
458 + 'label' => __('Test Email', 'easy-invoice'),
459 + 'type' => 'test_email',
460 + 'default' => '',
432 461 'col_span' => 'sm:col-span-6',
433 462 'description' => __('Send a test email to verify your email configuration is working correctly.', 'easy-invoice')
434 463 ],
435 464 ]
@@ -439,45 +468,13 @@
439 468 'description' => __('Configure email sent when an invoice is available for the client', 'easy-invoice'),
440 469 'fields' => [
441 470 'easy_invoice_invoice_email_enabled' => ['label' => __('Enable Invoice Available Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
442 471 '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'],
472 + 'easy_invoice_invoice_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'), 'col_span' => 'sm:col-span-6'],
476 473 'easy_invoice_invoice_test_template' => [
477 - 'label' => __('Test Invoice Email', 'easy-invoice'),
478 - 'type' => 'test_template_email',
479 - 'default' => '',
474 + 'label' => __('Test Invoice Email', 'easy-invoice'),
475 + 'type' => 'test_template_email',
476 + 'default' => '',
480 477 'col_span' => 'sm:col-span-6',
481 478 'description' => __('Send a test invoice email to verify the template and configuration.', 'easy-invoice'),
482 479 'template_type' => 'invoice_available'
483 480 ],
@@ -488,51 +485,13 @@
488 485 'description' => __('Configure email sent when a quote is available for the client', 'easy-invoice'),
489 486 'fields' => [
490 487 'easy_invoice_quote_email_enabled' => ['label' => __('Enable Quote Available Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
491 488 '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'],
489 + 'easy_invoice_quote_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => \EasyInvoice\Services\EmailManager::defaultTemplate('quote'), 'col_span' => 'sm:col-span-6'],
531 490 'easy_invoice_quote_test_template' => [
532 - 'label' => __('Test Quote Email', 'easy-invoice'),
533 - 'type' => 'test_template_email',
534 - 'default' => '',
491 + 'label' => __('Test Quote Email', 'easy-invoice'),
492 + 'type' => 'test_template_email',
493 + 'default' => '',
535 494 'col_span' => 'sm:col-span-6',
536 495 'description' => __('Send a test quote email to verify the template and configuration.', 'easy-invoice'),
537 496 'template_type' => 'quote_available'
538 497 ],
@@ -543,49 +502,13 @@
543 502 'description' => __('Configure email sent when a payment is received', 'easy-invoice'),
544 503 'fields' => [
545 504 'easy_invoice_payment_email_enabled' => ['label' => __('Enable Payment Received Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
546 505 '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>✅ Payment Received - Thank You!</h2>
548 -
549 -<p>Dear {{client_name}},</p>
550 -
551 -<div class="success-box">
552 - <p><strong>Payment Confirmation</strong><br>
553 - Invoice #{{invoice_number}}<br>
554 - <span class="amount-highlight">{{payment_amount}}</span><br>
555 - Payment Date: <strong>{{payment_date}}</strong><br>
556 - Payment Method: <strong>{{payment_method}}</strong></p>
557 -</div>
558 -
559 -<p>We have successfully received your payment. Thank you for your prompt payment!</p>
560 -
561 -<div class="info-box">
562 - <p><strong>📊 Payment Details:</strong><br>
563 - • Invoice Number: {{invoice_number}}<br>
564 - • Amount Paid: {{payment_amount}}<br>
565 - • Payment Date: {{payment_date}}<br>
566 - • Payment Method: {{payment_method}}<br>
567 - • Transaction ID: {{transaction_id}}</p>
568 -</div>
569 -
570 -<div class="highlight-box">
571 - <p><strong>🎉 Status: PAID</strong><br>
572 - Your payment has been processed and your account is now up to date. We appreciate your business!</p>
573 -</div>
574 -
575 -<p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
576 -
577 -<div class="divider"></div>
578 -
579 -<p>Thank you for choosing our services!</p>
580 -
581 -<p>Best regards,<br>
582 -<strong>{{company_name}}</strong><br>
583 -{{company_email}}</p>', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
506 + 'easy_invoice_payment_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => \EasyInvoice\Services\EmailManager::defaultTemplate('payment'), 'col_span' => 'sm:col-span-6'],
584 507 'easy_invoice_payment_test_template' => [
585 - 'label' => __('Test Payment Email', 'easy-invoice'),
586 - 'type' => 'test_template_email',
587 - 'default' => '',
508 + 'label' => __('Test Payment Email', 'easy-invoice'),
509 + 'type' => 'test_template_email',
510 + 'default' => '',
588 511 'col_span' => 'sm:col-span-6',
589 512 'description' => __('Send a test payment email to verify the template and configuration.', 'easy-invoice'),
590 513 'template_type' => 'payment_received'
591 514 ],
@@ -617,8 +540,9 @@
617 540 'easy_invoice_text_rate_price' => ['label' => __('Rate', 'easy-invoice'), 'type' => 'text', 'default' => __('Rate', 'easy-invoice'), 'col_span' => '', 'description' => __('Rate or price label', 'easy-invoice')],
618 541 'easy_invoice_text_adjust' => ['label' => __('Adjust', 'easy-invoice'), 'type' => 'text', 'default' => __('Adjust', 'easy-invoice'), 'col_span' => '', 'description' => __('Adjust label', 'easy-invoice')],
619 542 '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')],
620 543 'easy_invoice_text_total' => ['label' => __('Total', 'easy-invoice'), 'type' => 'text', 'default' => __('Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Total label', 'easy-invoice')],
544 + 'easy_invoice_text_line_total' => ['label' => __('Line total', 'easy-invoice'), 'type' => 'text', 'default' => __('Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Heading of the line-item amount column', 'easy-invoice')],
621 545 'easy_invoice_text_tax' => ['label' => __('Tax', 'easy-invoice'), 'type' => 'text', 'default' => __('Tax', 'easy-invoice'), 'col_span' => '', 'description' => __('Tax label', 'easy-invoice')],
622 546 'easy_invoice_text_discount' => ['label' => __('Discount', 'easy-invoice'), 'type' => 'text', 'default' => __('Discount', 'easy-invoice'), 'col_span' => '', 'description' => __('Discount label', 'easy-invoice')],
623 547 'easy_invoice_text_print' => ['label' => __('Print', 'easy-invoice'), 'type' => 'text', 'default' => __('Print', 'easy-invoice'), 'col_span' => '', 'description' => __('Print button text', 'easy-invoice')],
624 548 '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')],
@@ -623,9 +547,9 @@
623 547 'easy_invoice_text_print' => ['label' => __('Print', 'easy-invoice'), 'type' => 'text', 'default' => __('Print', 'easy-invoice'), 'col_span' => '', 'description' => __('Print button text', 'easy-invoice')],
624 548 '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')],
625 549 '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')],
626 550 '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')],
627 -
551 +
628 552 // Quote Text Settings
629 553 'easy_invoice_text_quote' => ['label' => __('Quote', 'easy-invoice'), 'type' => 'text', 'default' => __('Quote', 'easy-invoice'), 'col_span' => '', 'description' => __('Label for single quote', 'easy-invoice')],
630 554 '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')],
631 555 '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')],
@@ -640,10 +564,10 @@
640 564 'description' => __('Configure system preferences', 'easy-invoice'),
641 565 'icon' => 'fas fa-cog',
642 566 'fields' => [
643 567 'easy_invoice_date_format' => [
644 - 'label' => __('Date Format', 'easy-invoice'),
645 - 'type' => 'select',
568 + 'label' => __('Date Format', 'easy-invoice'),
569 + 'type' => 'select',
646 570 'default' => 'us',
647 571 'options' => [
648 572 'us' => __('MM/DD/YYYY (US) - 01/15/2024', 'easy-invoice'),
649 573 'uk' => __('DD/MM/YYYY (UK) - 15/01/2024', 'easy-invoice'),
@@ -653,8 +577,9 @@
653 577 ],
654 578
655 579 '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' ],
656 580 'easy_invoice_payment_reminder_days' => ['label' => __('Payment Reminder Days', 'easy-invoice'), 'type' => 'number', 'default' => 3, 'col_span' => 'sm:col-span-3'],
581 + 'easy_invoice_remove_data_on_uninstall' => ['label' => __('Remove all data when the plugin is deleted', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6', 'description' => __('Off, deleting the plugin from the Plugins screen keeps every invoice, quote, payment, client record and setting, so reinstalling picks up where you left off. On, all of it is removed — invoices are tax records, so leave this off unless you are certain.', 'easy-invoice')],
657 582 ]
658 583 ],
659 584 ];
660 585
@@ -659,25 +584,25 @@
659 584 ];
660 585
661 586 // Cache and filter the config
662 587 $this->config_cache = apply_filters('easy_invoice_settings_fields_config', $config);
663 -
588 +
664 589 return $this->config_cache;
665 590 }
666 -
591 +
667 592 /**
668 593 * Get all gateway settings configurations
669 - *
594 + *
670 595 * @return array Gateway settings configs
671 596 */
672 597 private function getGatewaySettingsConfigs(): array {
673 598 $gateway_configs = [];
674 -
599 +
675 600 try {
676 601 // Get gateway manager from the main plugin instance
677 602 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
678 603 $gateways = $gateway_manager->getGateways();
679 -
604 +
680 605 // Collect settings from each gateway
681 606 foreach ($gateways as $gateway_id => $gateway) {
682 607 try {
683 608 $gateway_configs[$gateway_id] = $gateway->getSettingsConfig();
@@ -687,12 +612,12 @@
687 612 }
688 613 }
689 614 } catch (\Exception $e) {
690 615 }
691 -
616 +
692 617 return $gateway_configs;
693 618 }
694 -
619 +
695 620 /**
696 621 * Prepare settings for display with proper filtering
697 622 *
698 623 * @param array $settings The settings array
@@ -700,12 +625,12 @@
700 625 */
701 626 public function prepareSettingsForDisplay($settings) {
702 627 return apply_filters('easy_invoice_settings_for_display', $settings);
703 628 }
704 -
629 +
705 630 /**
706 631 * Display method implementation
707 - *
632 + *
708 633 * @param array $args Display arguments
709 634 * @return void
710 635 */
711 636 public function display(array $args = []) {
@@ -710,12 +635,12 @@
710 635 */
711 636 public function display(array $args = []) {
712 637 $page = isset($args['page']) ? $args['page'] : '';
713 638 $subsection = isset($args['subsection']) ? $args['subsection'] : '';
714 -
639 +
715 640 // Clear cache to ensure new fields are recognized
716 641 $this->clearCache();
717 -
642 +
718 643 switch ($page) {
719 644 case PagesSlugs::SETTINGS:
720 645 if (!empty($subsection)) {
721 646 // Handle email settings subsections
@@ -723,18 +648,18 @@
723 648 } else {
724 649 $this->displaySettingsPage();
725 650 }
726 651 break;
727 -
652 +
728 653 default:
729 654 $this->displaySettingsPage();
730 655 break;
731 656 }
732 657 }
733 -
658 +
734 659 /**
735 660 * Display email settings page for specific subsection
736 - *
661 + *
737 662 * @param string $subsection The subsection to display
738 663 * @return void
739 664 */
740 665 protected function displayEmailSettingsPage($subsection) {
@@ -740,12 +665,12 @@
740 665 protected function displayEmailSettingsPage($subsection) {
741 666 // Get settings configuration
742 667 $settings_config = $this->get_settings_fields_config();
743 668 $settings = $this->getSettings();
744 -
669 +
745 670 // Filter to show only email settings
746 671 $email_config = $settings_config['email'] ?? [];
747 -
672 +
748 673 // Map subsection to the appropriate subsection in email config
749 674 $subsection_mapping = [
750 675 PagesSlugs::EMAIL_SETTINGS_GENERAL => 'general',
751 676 PagesSlugs::EMAIL_SETTINGS_INVOICE => 'invoice_available',
@@ -752,11 +677,11 @@
752 677 PagesSlugs::EMAIL_SETTINGS_QUOTE => 'quote_available',
753 678 PagesSlugs::EMAIL_SETTINGS_PAYMENT => 'payment_received',
754 679 PagesSlugs::EMAIL_SETTINGS_PAYMENT_REMINDER => 'payment_reminder',
755 680 ];
756 -
681 +
757 682 $target_subsection = $subsection_mapping[$subsection] ?? 'general';
758 -
683 +
759 684 // Create a modified config with only the target subsection
760 685 $filtered_config = [
761 686 'email' => [
762 687 'title' => __('Email Settings', 'easy-invoice'),
@@ -766,13 +691,13 @@
766 691 $target_subsection => $email_config['subsections'][$target_subsection] ?? []
767 692 ]
768 693 ]
769 694 ];
770 -
695 +
771 696 // Include the settings page template with filtered config
772 697 include EASY_INVOICE_PLUGIN_DIR . 'templates/settings-page.php';
773 698 }
774 -
699 +
775 700 /**
776 701 * Display settings page
777 702 *
778 703 * @return void
@@ -777,23 +702,23 @@
777 702 *
778 703 * @return void
779 704 */
780 705 protected function displaySettingsPage() {
781 - $settings = $this->getSettings();
782 -
706 + $settings = $this->getSettings();
707 +
783 708 // Allow developers to modify settings before display
784 709 $settings = apply_filters('easy_invoice_before_display_settings', $settings);
785 -
710 +
786 711 // Ensure payment methods are set (this is critical for gateway checkboxes)
787 712 if (!isset($settings['easy_invoice_payment_methods']) || !is_array($settings['easy_invoice_payment_methods'])) {
788 713 $settings['easy_invoice_payment_methods'] = get_option('easy_invoice_payment_methods', []);
789 714 }
790 -
715 +
791 716 // Ensure gateway order is set
792 717 if (!isset($settings['easy_invoice_payment_gateway_order']) || !is_array($settings['easy_invoice_payment_gateway_order'])) {
793 718 $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []);
794 719 }
795 -
720 +
796 721 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
797 722 $all_gateways_unsorted = $gateway_manager->getGateways();
798 723 $gateway_order_option = $settings['easy_invoice_payment_gateway_order'];
799 724 $payment_methods_enabled = $settings['easy_invoice_payment_methods'];
@@ -807,29 +732,29 @@
807 732 }
808 733 }
809 734 }
810 735 $all_gateways_sorted = array_merge($all_gateways_sorted, $all_gateways_unsorted);
811 -
736 +
812 737 $settings_config = $this->get_settings_fields_config();
813 738
814 739 // Prepare template variables
815 740 $template_args = apply_filters('easy_invoice_settings_template_args', [
816 - 'settings' => $settings,
817 - 'settings_config' => $settings_config,
818 - 'all_gateways_sorted' => $all_gateways_sorted,
819 - 'payment_methods_enabled' => $payment_methods_enabled
741 + 'settings' => $settings,
742 + 'settings_config' => $settings_config,
743 + 'all_gateways_sorted' => $all_gateways_sorted,
744 + 'payment_methods_enabled' => $payment_methods_enabled
820 745 ]);
821 -
746 +
822 747 // Display the template with template args
823 748 $this->displayTemplate(
824 749 apply_filters('easy_invoice_settings_template_path', EASY_INVOICE_PLUGIN_DIR . 'templates/settings-page.php'),
825 750 $template_args
826 751 );
827 -
752 +
828 753 // Action for plugins to add their own content after the settings page
829 754 do_action('easy_invoice_after_settings_page');
830 755 }
831 -
756 +
832 757 /**
833 758 * Save payment settings
834 759 *
835 760 * @param array $posted_settings Posted settings
@@ -845,9 +770,9 @@
845 770 } else {
846 771 update_option('easy_invoice_payment_methods', []);
847 772 $response['saved_options']['easy_invoice_payment_methods'] = [];
848 773 }
849 -
774 +
850 775 // Handle gateway order
851 776 if (isset($posted_settings['easy_invoice_gateway_order']) && is_array($posted_settings['easy_invoice_gateway_order'])) {
852 777 $gateway_order = array_map('sanitize_key', $posted_settings['easy_invoice_gateway_order']);
853 778 update_option('easy_invoice_payment_gateway_order', $gateway_order);
@@ -855,13 +780,13 @@
855 780 } else {
856 781 update_option('easy_invoice_payment_gateway_order', []);
857 782 $response['saved_options']['easy_invoice_gateway_order'] = [];
858 783 }
859 -
784 +
860 785 // Handle gateway display names
861 786 $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager();
862 787 $all_gateways = $gateway_manager->getGateways();
863 -
788 +
864 789 foreach ($all_gateways as $gateway_id => $gateway) {
865 790 $display_name_key = 'easy_invoice_gateway_display_name_' . $gateway_id;
866 791 if (isset($posted_settings[$display_name_key])) {
867 792 $display_name = wp_strip_all_tags(wp_unslash($posted_settings[$display_name_key]));
@@ -868,9 +793,9 @@
868 793 update_option($display_name_key, $display_name);
869 794 $response['saved_options'][$display_name_key] = $display_name;
870 795 }
871 796 }
872 -
797 +
873 798 // Handle gateway-specific settings
874 799 try {
875 800 $gateway_configs = $this->getGatewaySettingsConfigs();
876 801 foreach ($gateway_configs as $gateway_id => $gateway_config) {
@@ -896,21 +821,25 @@
896 821 *
897 822 * @return void
898 823 */
899 824 public function saveSettings() {
900 - // Verify nonce
901 - if (!wp_verify_nonce($_POST['easy_invoice_settings_nonce'], 'easy_invoice_settings')) {
902 - wp_die(__('Security check failed', 'easy-invoice'));
825 + // Verify nonce. $_POST['easy_invoice_settings_nonce'] was read unguarded, so a
826 + // request without it raised "Undefined array key" before the check could run.
827 + $nonce = isset($_POST['easy_invoice_settings_nonce'])
828 + ? sanitize_text_field(wp_unslash($_POST['easy_invoice_settings_nonce']))
829 + : '';
830 + if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) {
831 + wp_die(esc_html__('Security check failed', 'easy-invoice'));
903 832 }
904 -
905 -
833 +
834 +
906 835 // Check permissions
907 836 if (!current_user_can('manage_options')) {
908 - wp_die(__('You do not have permission to perform this action', 'easy-invoice'));
837 + wp_die(esc_html__('You do not have permission to perform this action', 'easy-invoice'));
909 838 }
910 -
839 +
911 840 $posted_settings = $_POST['settings'] ?? [];
912 -
841 +
913 842 $settings_config = $this->get_settings_fields_config();
914 843 $response = [
915 844 'success' => true,
916 845 'message' => __('Settings saved successfully', 'easy-invoice'),
@@ -915,9 +844,9 @@
915 844 'success' => true,
916 845 'message' => __('Settings saved successfully', 'easy-invoice'),
917 846 'saved_options' => []
918 847 ];
919 -
848 +
920 849 try {
921 850 // Process each section
922 851 foreach ($settings_config as $section_id => $section_data) {
923 852 if (isset($section_data['fields']) && is_array($section_data['fields'])) {
@@ -947,37 +876,60 @@
947 876 }
948 877 }
949 878 }
950 879 }
951 -
880 +
881 + // Fields other code adds to the Tax section for display only
882 + // (`easy_invoice_tax_section_additional_fields`) are saved with the
883 + // same rules — the reverse-charge switch lived only there and could
884 + // never be turned on from the screen.
885 + $extra_tax_fields = apply_filters('easy_invoice_tax_section_additional_fields', [], []);
886 + $known = [];
887 + foreach ($settings_config as $section_data) {
888 + foreach ((array) ($section_data['fields'] ?? []) as $k => $unused) { $known[$k] = true; }
889 + foreach ((array) ($section_data['subsections'] ?? []) as $sub) { foreach ((array) ($sub['fields'] ?? []) as $k => $unused) { $known[$k] = true; } }
890 + }
891 + foreach ((array) $extra_tax_fields as $option_key => $field_config) {
892 + if (!is_string($option_key) || isset($known[$option_key]) || !is_array($field_config)) {
893 + continue;
894 + }
895 + if (isset($posted_settings[$option_key])) {
896 + $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response);
897 + } elseif (($field_config['type'] ?? '') === 'checkbox') {
898 + update_option($option_key, 'no');
899 + $response['saved_options'][$option_key] = 'no';
900 + }
901 + }
902 +
952 903 // Handle special sections like payment methods
953 904 if (isset($settings_config['payment']) && isset($settings_config['payment']['is_special_section'])) {
954 905 $this->savePaymentSettings($posted_settings, $response);
955 906 }
956 -
907 +
957 908 // Clear any caches
958 909 wp_cache_flush();
959 -
910 +
960 911 // Clear settings cache
961 912 $this->settings_cache = null;
962 -
913 + self::$shared_settings_cache = null;
914 +
963 915 // Log the save action
964 916 $this->log('Settings saved successfully by user: ' . get_current_user_id());
965 -
917 +
966 918 } catch (\Exception $e) {
967 919 $response['success'] = false;
968 920 $response['message'] = __('Error saving settings: ', 'easy-invoice') . $e->getMessage();
969 921 $this->log('Error saving settings: ' . $e->getMessage());
970 -
922 +
971 923 }
972 -
924 +
973 925 // Clear settings cache to ensure new fields are recognized
974 926 $this->clearCache();
975 -
927 +
976 928 // Send JSON response
977 929 wp_send_json($response);
978 930 }
979 -
931 +
980 932 /**
981 933 * Sanitize and save a single setting
982 934 *
983 935 * @param string $option_key The option key
@@ -988,19 +940,19 @@
988 940 */
989 941 private function sanitizeAndSaveSetting($option_key, $field_config, $value, &$response) {
990 942 if (in_array($option_key, [
991 943 'easy_invoice_quote_accept_text',
992 - 'easy_invoice_quote_accepted_message',
944 + 'easy_invoice_quote_accepted_message',
993 945 'easy_invoice_quote_declined_message'
994 946 ])) {
995 947 }
996 -
948 +
997 949 // Unslash the value to prevent double-escaping
998 950 $value = wp_unslash($value);
999 -
951 +
1000 952 // Allow pre-sanitization filters
1001 953 $value = apply_filters('easy_invoice_pre_sanitize_option', $value, $option_key, $field_config);
1002 -
954 +
1003 955 // Sanitize based on field type
1004 956 switch ($field_config['type']) {
1005 957 case 'email':
1006 958 $value = sanitize_email($value);
@@ -1051,21 +1003,21 @@
1051 1003 // For regular text fields, use wp_strip_all_tags to prevent double-escaping
1052 1004 $value = wp_strip_all_tags($value);
1053 1005 break;
1054 1006 }
1055 -
1007 +
1056 1008 // Allow post-sanitization value modification
1057 1009 $value = apply_filters("easy_invoice_sanitize_option_{$option_key}", $value, $field_config);
1058 1010 $value = apply_filters('easy_invoice_sanitize_option', $value, $option_key, $field_config);
1059 -
1011 +
1060 1012 // Save the setting normally
1061 1013 update_option($option_key, $value);
1062 1014 $response['saved_options'][$option_key] = $value;
1063 1015 }
1064 -
1016 +
1065 1017 /**
1066 1018 * Get all settings for the plugin
1067 - *
1019 + *
1068 1020 * @param bool $use_cache Whether to use cached settings
1069 1021 * @return array The settings array
1070 1022 */
1071 1023 public function getSettings($use_cache = true): array {
@@ -1072,9 +1024,12 @@
1072 1024 // Return cached settings if available
1073 1025 if ($use_cache && is_array($this->settings_cache)) {
1074 1026 return $this->settings_cache;
1075 1027 }
1076 -
1028 + if ($use_cache && is_array(self::$shared_settings_cache)) {
1029 + return $this->settings_cache = self::$shared_settings_cache;
1030 + }
1031 +
1077 1032 $settings = [];
1078 1033 $config = $this->get_settings_fields_config();
1079 1034
1080 1035 foreach ($config as $section_id => $section_data) {
@@ -1080,8 +1035,15 @@
1080 1035 foreach ($config as $section_id => $section_data) {
1081 1036 if ($section_id === 'payment' && isset($section_data['gateways'])) {
1082 1037 // Handle gateway fields specifically
1083 1038 foreach ($section_data['gateways'] as $gateway_id => $gateway_config) {
1039 + // The per-gateway display name is saved by savePaymentSettings()
1040 + // but was never loaded back, so the field always showed the default.
1041 + $display_key = 'easy_invoice_gateway_display_name_' . $gateway_id;
1042 + $display_val = get_option($display_key, '');
1043 + if ('' !== $display_val) {
1044 + $settings[$display_key] = $display_val;
1045 + }
1084 1046 if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) {
1085 1047 foreach ($gateway_config['fields'] as $option_key => $field_data) {
1086 1048 $default_value = $field_data['default'] ?? '';
1087 1049 $settings[$option_key] = get_option($option_key, $default_value);
@@ -1094,9 +1056,9 @@
1094 1056 foreach ($section_data['subsections'] as $subsection_id => $subsection_data) {
1095 1057 if (isset($subsection_data['fields']) && is_array($subsection_data['fields'])) {
1096 1058 foreach ($subsection_data['fields'] as $option_key => $field_data) {
1097 1059 $default_value = $field_data['default'] ?? '';
1098 -
1060 +
1099 1061 // Handle special defaults
1100 1062 if ($option_key === 'easy_invoice_email_from_name' && $default_value === get_bloginfo('name')) {
1101 1063 $default_value = get_bloginfo('name');
1102 1064 }
@@ -1102,9 +1064,9 @@
1102 1064 }
1103 1065 if ($option_key === 'easy_invoice_email_from_address' && $default_value === get_bloginfo('admin_email')) {
1104 1066 $default_value = get_bloginfo('admin_email');
1105 1067 }
1106 -
1068 +
1107 1069 // Check if field has a value_callback function
1108 1070 if (isset($field_data['value_callback']) && is_callable($field_data['value_callback'])) {
1109 1071 $settings[$option_key] = $field_data['value_callback']();
1110 1072 } else {
@@ -1117,9 +1079,9 @@
1117 1079 }
1118 1080 else if (isset($section_data['fields']) && is_array($section_data['fields'])) {
1119 1081 foreach ($section_data['fields'] as $option_key => $field_data) {
1120 1082 $default_value = $field_data['default'] ?? '';
1121 -
1083 +
1122 1084 // Handle special defaults
1123 1085 if ($option_key === 'easy_invoice_email_from_name' && $default_value === get_bloginfo('name')) {
1124 1086 $default_value = get_bloginfo('name');
1125 1087 }
@@ -1125,9 +1087,9 @@
1125 1087 }
1126 1088 if ($option_key === 'easy_invoice_email_from_address' && $default_value === get_bloginfo('admin_email')) {
1127 1089 $default_value = get_bloginfo('admin_email');
1128 1090 }
1129 -
1091 +
1130 1092 // Check if field has a value_callback function
1131 1093 if (isset($field_data['value_callback']) && is_callable($field_data['value_callback'])) {
1132 1094 $settings[$option_key] = $field_data['value_callback']();
1133 1095 } else {
@@ -1136,9 +1098,9 @@
1136 1098 }
1137 1099 }
1138 1100 }
1139 1101 }
1140 -
1102 +
1141 1103 // Add special array settings
1142 1104 $settings['easy_invoice_payment_methods'] = get_option('easy_invoice_payment_methods', []);
1143 1105 $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []);
1144 1106
@@ -1143,9 +1105,10 @@
1143 1105 $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []);
1144 1106
1145 1107 // Cache settings
1146 1108 $this->settings_cache = $settings;
1147 -
1109 + self::$shared_settings_cache = $settings;
1110 +
1148 1111 // Allow third party to add/modify settings
1149 1112 return apply_filters('easy_invoice_settings', $settings);
1150 1113 }
1151 1114
@@ -1155,9 +1118,9 @@
1155 1118 * @param string $message The message to log
1156 1119 * @return void
1157 1120 */
1158 1121 protected function log(string $message): void {
1159 -
1122 +
1160 1123 }
1161 1124
1162 1125 /**
1163 1126 * Initialize default settings
@@ -1162,55 +1125,81 @@
1162 1125 /**
1163 1126 * Initialize default settings
1164 1127 */
1165 1128 public function initializeSettings() {
1129 + // One-time migration (2.1.22): strip the leftover
1130 + // "<strong>Shortcode:</strong> <code>[easy_(invoice|quote)_url …]</code>"
1131 + // reference line from saved email bodies. Originally intended as admin-side
1132 + // documentation, but it lived inside the email template and got emailed to
1133 + // clients verbatim. Guarded by a flag option so each install runs at most once.
1134 + //
1135 + // The regex tolerates the optional surrounding <p>...</p> because WP's
1136 + // wp_kses_post may normalise the wrapping when the option is saved.
1137 + if (!get_option('easy_invoice_email_shortcode_hint_removed_v2')) {
1138 + foreach (['easy_invoice_invoice_email_body', 'easy_invoice_quote_email_body'] as $opt_key) {
1139 + $body = get_option($opt_key, '');
1140 + if (!is_string($body) || $body === '') {
1141 + continue;
1142 + }
1143 + $cleaned = preg_replace(
1144 + '#\s*(?:<p>\s*)?<strong>\s*Shortcode\s*:\s*</strong>\s*<code>\s*\[easy_(?:invoice|quote)_url[^\]]*\]\s*</code>(?:\s*</p>)?\s*#i',
1145 + '',
1146 + $body
1147 + );
1148 + if ($cleaned !== null && $cleaned !== $body) {
1149 + update_option($opt_key, $cleaned);
1150 + }
1151 + }
1152 + update_option('easy_invoice_email_shortcode_hint_removed_v2', 1);
1153 + }
1154 +
1166 1155 // Initialize invoice number settings if not already set
1167 1156 if (!get_option('easy_invoice_invoice_prefix')) {
1168 1157 update_option('easy_invoice_invoice_prefix', 'INV-');
1169 1158 }
1170 -
1159 +
1171 1160 if (!get_option('easy_invoice_next_invoice_number')) {
1172 1161 update_option('easy_invoice_next_invoice_number', 1);
1173 1162 }
1174 -
1163 +
1175 1164 // Initialize other default settings
1176 1165 if (!get_option('easy_invoice_invoice_due_days')) {
1177 1166 update_option('easy_invoice_invoice_due_days', 30);
1178 1167 }
1179 -
1168 +
1180 1169 if (!get_option('easy_invoice_currency_code')) {
1181 1170 update_option('easy_invoice_currency_code', 'USD');
1182 1171 }
1183 -
1172 +
1184 1173 if (!get_option('easy_invoice_currency_symbol')) {
1185 1174 update_option('easy_invoice_currency_symbol', '$');
1186 1175 }
1187 -
1176 +
1188 1177 if (!get_option('easy_invoice_currency_position')) {
1189 1178 update_option('easy_invoice_currency_position', 'left');
1190 1179 }
1191 -
1180 +
1192 1181 if (!get_option('easy_invoice_currency_symbol_type')) {
1193 1182 update_option('easy_invoice_currency_symbol_type', 'symbol');
1194 1183 }
1195 -
1184 +
1196 1185 if (!get_option('easy_invoice_decimal_separator')) {
1197 1186 update_option('easy_invoice_decimal_separator', '.');
1198 1187 }
1199 -
1188 +
1200 1189 if (!get_option('easy_invoice_thousands_separator')) {
1201 1190 update_option('easy_invoice_thousands_separator', ',');
1202 1191 }
1203 -
1192 +
1204 1193 if (!get_option('easy_invoice_decimal_precision')) {
1205 1194 update_option('easy_invoice_decimal_precision', 2);
1206 1195 }
1207 -
1196 +
1208 1197 // Initialize date format setting
1209 1198 if (!get_option('easy_invoice_date_format')) {
1210 1199 update_option('easy_invoice_date_format', 'us');
1211 1200 }
1212 -
1201 +
1213 1202 // Fix legacy date format values
1214 1203 $current_date_format = get_option('easy_invoice_date_format');
1215 1204 if ($current_date_format === 'mdy' || $current_date_format === 'm/d/Y') {
1216 1205 update_option('easy_invoice_date_format', 'us');
@@ -1218,30 +1207,30 @@
1218 1207 update_option('easy_invoice_date_format', 'uk');
1219 1208 } elseif ($current_date_format === 'Y-m-d') {
1220 1209 update_option('easy_invoice_date_format', 'iso');
1221 1210 }
1222 -
1211 +
1223 1212 // Initialize email settings
1224 1213 if (!get_option('easy_invoice_email_from_name')) {
1225 1214 update_option('easy_invoice_email_from_name', get_bloginfo('name'));
1226 1215 }
1227 -
1216 +
1228 1217 if (!get_option('easy_invoice_email_from_address')) {
1229 1218 update_option('easy_invoice_email_from_address', get_bloginfo('admin_email'));
1230 1219 }
1231 -
1220 +
1232 1221 if (!get_option('easy_invoice_enable_email_styling')) {
1233 1222 update_option('easy_invoice_enable_email_styling', 'yes');
1234 1223 }
1235 -
1224 +
1236 1225 if (!get_option('easy_invoice_bcc_admin')) {
1237 1226 update_option('easy_invoice_bcc_admin', 'no');
1238 1227 }
1239 -
1228 +
1240 1229 if (!get_option('easy_invoice_admin_email')) {
1241 1230 update_option('easy_invoice_admin_email', get_option('admin_email'));
1242 1231 }
1243 -
1232 +
1244 1233 // Initialize Text Settings
1245 1234 $text_settings = [
1246 1235 'easy_invoice_text_invoice' => __('Invoice', 'easy-invoice'),
1247 1236 'easy_invoice_text_invoices' => __('Invoices', 'easy-invoice'),
@@ -1257,8 +1246,9 @@
1257 1246 'easy_invoice_text_rate_price' => __('Rate', 'easy-invoice'),
1258 1247 'easy_invoice_text_adjust' => __('Adjust', 'easy-invoice'),
1259 1248 'easy_invoice_text_sub_total' => __('Sub Total', 'easy-invoice'),
1260 1249 'easy_invoice_text_total' => __('Total', 'easy-invoice'),
1250 + 'easy_invoice_text_line_total' => __('Total', 'easy-invoice'),
1261 1251 'easy_invoice_text_tax' => __('Tax', 'easy-invoice'),
1262 1252 'easy_invoice_text_discount' => __('Discount', 'easy-invoice'),
1263 1253 'easy_invoice_text_page' => __('Page', 'easy-invoice'),
1264 1254 'easy_invoice_text_print' => __('Print', 'easy-invoice'),
@@ -1282,155 +1272,49 @@
1282 1272 'easy_invoice_text_paid' => __('Paid', 'easy-invoice'),
1283 1273 'easy_invoice_text_unpaid' => __('Unpaid', 'easy-invoice'),
1284 1274 'easy_invoice_text_cancelled' => __('Cancelled', 'easy-invoice'),
1285 1275 ];
1286 -
1276 +
1287 1277 foreach ($text_settings as $option_key => $default_value) {
1288 1278 if (!get_option($option_key)) {
1289 1279 update_option($option_key, $default_value);
1290 1280 }
1291 1281 }
1292 -
1282 +
1293 1283 if (!get_option('easy_invoice_invoice_email_enabled')) {
1294 1284 update_option('easy_invoice_invoice_email_enabled', 'yes');
1295 1285 }
1296 -
1286 +
1297 1287 if (!get_option('easy_invoice_invoice_email_subject')) {
1298 1288 update_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1299 1289 }
1300 -
1290 +
1301 1291 if (!get_option('easy_invoice_invoice_email_body')) {
1302 - update_option('easy_invoice_invoice_email_body', __('<h2>📄 Your Invoice is Ready</h2>
1292 + update_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'));
1293 + }
1303 1294
1304 -<p>Dear {{client_name}},</p>
1305 -
1306 -<div class="highlight-box">
1307 - <p><strong>Invoice #{{invoice_number}}</strong><br>
1308 - <span class="amount-highlight">{{total_amount}}</span><br>
1309 - Due Date: <strong>{{due_date}}</strong></p>
1310 -</div>
1311 -
1312 -<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>
1313 -
1314 -<div class="info-box">
1315 - <p><strong>📋 Payment Details:</strong><br>
1316 - • Invoice Number: {{invoice_number}}<br>
1317 - • Total Amount: {{total_amount}}<br>
1318 - • Due Date: {{due_date}}<br>
1319 - • Payment Terms: {{payment_terms}}</p>
1320 -</div>
1321 -
1322 -<div class="highlight-box">
1323 - <p><strong>🔗 View Invoice Online:</strong><br>
1324 - <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1325 - <p><strong>Shortcode:</strong> <code>[easy_invoice_url number="{{invoice_number}}" text="View Invoice"]</code></p>
1326 -</div>
1327 -
1328 -<div class="warning-box">
1329 - <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1330 -</div>
1331 -
1332 -<p>If you have any questions about this invoice, please don\'t hesitate to contact us.</p>
1333 -
1334 -<div class="divider"></div>
1335 -
1336 -<p>Thank you for your business!</p>
1337 -
1338 -<p>Best regards,<br>
1339 -<strong>{{company_name}}</strong><br>
1340 -{{company_email}}</p>', 'easy-invoice'));
1341 - }
1342 -
1343 1295 if (!get_option('easy_invoice_quote_email_enabled')) {
1344 1296 update_option('easy_invoice_quote_email_enabled', 'yes');
1345 1297 }
1346 -
1298 +
1347 1299 if (!get_option('easy_invoice_quote_email_subject')) {
1348 1300 update_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'));
1349 1301 }
1350 -
1302 +
1351 1303 if (!get_option('easy_invoice_quote_email_body')) {
1352 - update_option('easy_invoice_quote_email_body', __('<h2>Your Quote is Ready</h2>
1304 + update_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote'));
1305 + }
1353 1306
1354 -<p>Dear {{client_name}},</p>
1355 -
1356 -<div class="highlight-box">
1357 - <p><strong>Quote #{{quote_number}}</strong><br>
1358 - Total Amount: <strong>{{total_amount}}</strong><br>
1359 - Valid Until: <strong>{{expiry_date}}</strong></p>
1360 -</div>
1361 -
1362 -<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>
1363 -
1364 -<div class="info-box">
1365 - <p><strong>Quote Summary:</strong><br>
1366 - • Quote Number: {{quote_number}}<br>
1367 - • Total Amount: {{total_amount}}<br>
1368 - • Valid Until: {{expiry_date}}<br>
1369 - • Terms: {{payment_terms}}</p>
1370 -</div>
1371 -
1372 -<div class="highlight-box">
1373 - <p><strong>🔗 View Quote Online:</strong><br>
1374 - <a href="{{quote_url}}" style="color: #3b82f6; text-decoration: underline;">{{quote_url}}</a></p>
1375 - <p><strong>Shortcode:</strong> <code>[easy_quote_url number="{{quote_number}}" text="View Quote"]</code></p>
1376 -</div>
1377 -
1378 -<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>
1379 -
1380 -<p>We look forward to working with you!</p>
1381 -
1382 -<p>Best regards,<br>
1383 -<strong>{{company_name}}</strong><br>
1384 -{{company_email}}</p>', 'easy-invoice'));
1385 - }
1386 -
1387 1307 if (!get_option('easy_invoice_payment_email_enabled')) {
1388 1308 update_option('easy_invoice_payment_email_enabled', 'yes');
1389 1309 }
1390 -
1310 +
1391 1311 if (!get_option('easy_invoice_payment_email_subject')) {
1392 1312 update_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'));
1393 1313 }
1394 -
1314 +
1395 1315 if (!get_option('easy_invoice_payment_email_body')) {
1396 - update_option('easy_invoice_payment_email_body', __('<h2>✅ Payment Received - Thank You!</h2>
1397 -
1398 -<p>Dear {{client_name}},</p>
1399 -
1400 -<div class="success-box">
1401 - <p><strong>Payment Confirmation</strong><br>
1402 - Invoice #{{invoice_number}}<br>
1403 - <span class="amount-highlight">{{payment_amount}}</span><br>
1404 - Payment Date: <strong>{{payment_date}}</strong><br>
1405 - Payment Method: <strong>{{payment_method}}</strong></p>
1406 -</div>
1407 -
1408 -<p>We have successfully received your payment. Thank you for your prompt payment!</p>
1409 -
1410 -<div class="info-box">
1411 - <p><strong>📊 Payment Details:</strong><br>
1412 - • Invoice Number: {{invoice_number}}<br>
1413 - • Amount Paid: {{payment_amount}}<br>
1414 - • Payment Date: {{payment_date}}<br>
1415 - • Payment Method: {{payment_method}}<br>
1416 - • Transaction ID: {{transaction_id}}</p>
1417 -</div>
1418 -
1419 -<div class="highlight-box">
1420 - <p><strong>🎉 Status: PAID</strong><br>
1421 - Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1422 -</div>
1423 -
1424 -<p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
1425 -
1426 -<div class="divider"></div>
1427 -
1428 -<p>Thank you for choosing our services!</p>
1429 -
1430 -<p>Best regards,<br>
1431 -<strong>{{company_name}}</strong><br>
1432 -{{company_email}}</p>', 'easy-invoice'));
1316 + update_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment'));
1433 1317 }
1434 1318 }
1435 1319
1436 1320 /**
@@ -1440,24 +1324,24 @@
1440 1324 // Verify nonce
1441 1325 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) {
1442 1326 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
1443 1327 }
1444 -
1328 +
1445 1329 // Check permissions
1446 1330 if (!current_user_can('manage_options')) {
1447 1331 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
1448 1332 }
1449 -
1333 +
1450 1334 // Get test email address
1451 1335 $test_email = sanitize_email($_POST['test_email'] ?? '');
1452 1336 if (empty($test_email)) {
1453 1337 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1454 1338 }
1455 -
1339 +
1456 1340 // Test email sending
1457 1341 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
1458 1342 $result = $email_manager->testEmail($test_email);
1459 -
1343 +
1460 1344 if ($result['success']) {
1461 1345 wp_send_json_success($result);
1462 1346 } else {
1463 1347 wp_send_json_error($result);
@@ -1462,9 +1346,9 @@
1462 1346 } else {
1463 1347 wp_send_json_error($result);
1464 1348 }
1465 1349 }
1466 -
1350 +
1467 1351 /**
1468 1352 * Test payment reminder email functionality
1469 1353 */
1470 1354 public function testPaymentReminderEmail(): void {
@@ -1471,28 +1355,28 @@
1471 1355 // Verify nonce
1472 1356 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) {
1473 1357 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
1474 1358 }
1475 -
1359 +
1476 1360 // Check permissions
1477 1361 if (!current_user_can('manage_options')) {
1478 1362 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
1479 1363 }
1480 -
1364 +
1481 1365 // Get test email address
1482 1366 $test_email = sanitize_email($_POST['test_email'] ?? '');
1483 1367 if (empty($test_email)) {
1484 1368 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1485 1369 }
1486 -
1370 +
1487 1371 // Get payment reminder settings
1488 - $subject = get_option('easy_invoice_pro_payment_reminder_subject', __('A friendly reminder - Invoice #{{invoice_number}}', 'easy-invoice-pro'));
1372 + $subject = get_option('easy_invoice_pro_payment_reminder_subject', __('A friendly reminder - Invoice #{{invoice_number}}', 'easy-invoice'));
1489 1373 $body = get_option('easy_invoice_pro_payment_reminder_body', '');
1490 -
1374 +
1491 1375 if (empty($body)) {
1492 1376 wp_send_json_error(['message' => __('Payment reminder email template is empty. Please configure the email message first.', 'easy-invoice')]);
1493 1377 }
1494 -
1378 +
1495 1379 // Create sample data for testing
1496 1380 $sample_data = [
1497 1381 'invoice_number' => 'TEST-001',
1498 1382 'client_name' => 'Test Client',
@@ -1498,21 +1382,22 @@
1498 1382 'client_name' => 'Test Client',
1499 1383 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1500 1384 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1501 1385 'total_amount' => '$1,000.00',
1502 - 'due_date' => date('Y-m-d', strtotime('+7 days')),
1386 + 'amount_due' => '$1,000.00',
1387 + 'due_date' => wp_date('Y-m-d', strtotime('+7 days')),
1503 1388 ];
1504 -
1389 +
1505 1390 // Replace placeholders
1506 1391 foreach ($sample_data as $placeholder => $value) {
1507 1392 $subject = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $subject);
1508 1393 $body = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $body);
1509 1394 }
1510 -
1395 +
1511 1396 // Test email sending
1512 1397 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
1513 1398 $result = $email_manager->sendTestTemplateEmail($test_email, $subject, $body);
1514 -
1399 +
1515 1400 if ($result['success']) {
1516 1401 wp_send_json_success($result);
1517 1402 } else {
1518 1403 wp_send_json_error($result);
@@ -1517,9 +1402,9 @@
1517 1402 } else {
1518 1403 wp_send_json_error($result);
1519 1404 }
1520 1405 }
1521 -
1406 +
1522 1407 /**
1523 1408 * Test template email functionality
1524 1409 */
1525 1410 public function testTemplateEmail(): void {
@@ -1526,30 +1411,30 @@
1526 1411 // Verify nonce
1527 1412 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_settings')) {
1528 1413 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
1529 1414 }
1530 -
1415 +
1531 1416 // Check permissions
1532 1417 if (!current_user_can('manage_options')) {
1533 1418 wp_send_json_error(['message' => __('You do not have permission to perform this action', 'easy-invoice')]);
1534 1419 }
1535 -
1420 +
1536 1421 // Get test email address and template type
1537 1422 $test_email = sanitize_email($_POST['test_email'] ?? '');
1538 1423 $template_type = sanitize_text_field($_POST['template_type'] ?? '');
1539 -
1424 +
1540 1425 if (empty($test_email)) {
1541 1426 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1542 1427 }
1543 -
1428 +
1544 1429 if (empty($template_type)) {
1545 1430 wp_send_json_error(['message' => __('Template type is required', 'easy-invoice')]);
1546 1431 }
1547 -
1432 +
1548 1433 // Get the actual configured email settings
1549 1434 $subject = '';
1550 1435 $body = '';
1551 -
1436 +
1552 1437 switch ($template_type) {
1553 1438 case 'invoice_available':
1554 1439 $subject = get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1555 1440 $body = get_option('easy_invoice_invoice_email_body', self::getInvoiceEmailBody());
@@ -1564,17 +1449,17 @@
1564 1449 break;
1565 1450 default:
1566 1451 wp_send_json_error(['message' => __('Invalid template type', 'easy-invoice')]);
1567 1452 }
1568 -
1453 +
1569 1454 // Ensure we have valid strings
1570 1455 $subject = is_string($subject) ? $subject : '';
1571 1456 $body = is_string($body) ? $body : '';
1572 -
1457 +
1573 1458 if (empty($subject) || empty($body)) {
1574 1459 wp_send_json_error(['message' => __('Email template is empty or invalid', 'easy-invoice')]);
1575 1460 }
1576 -
1461 +
1577 1462 // Create sample data for testing
1578 1463 $sample_data = [
1579 1464 'invoice_number' => 'TEST-001',
1580 1465 'quote_number' => 'TEST-Q-001',
@@ -1580,33 +1465,53 @@
1580 1465 'quote_number' => 'TEST-Q-001',
1581 1466 'client_name' => 'Test Client',
1582 1467 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1583 1468 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1584 - 'total_amount' => '$1,000.00',
1585 - 'payment_amount' => '$1,000.00',
1586 - 'due_date' => date('Y-m-d', strtotime('+30 days')),
1587 - 'expiry_date' => date('Y-m-d', strtotime('+30 days')),
1588 - 'payment_date' => date('Y-m-d'),
1469 + 'total_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1470 + 'payment_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1471 + 'due_date' => wp_date('Y-m-d', strtotime('+30 days')),
1472 + 'expiry_date' => wp_date('Y-m-d', strtotime('+30 days')),
1473 + 'payment_date' => current_time('Y-m-d'),
1589 1474 'payment_method' => 'Credit Card',
1590 1475 'transaction_id' => 'TXN-' . uniqid(),
1591 1476 'payment_terms' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
1477 + // Everything the stock templates can mention, so a test never shows a raw tag.
1478 + 'amount_due' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1479 + 'subtotal' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1480 + 'tax_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(0) : '$0.00',
1481 + 'discount_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(0) : '$0.00',
1482 + 'issue_date' => current_time('Y-m-d'),
1483 + 'invoice_title' => __('Sample invoice', 'easy-invoice'),
1484 + 'quote_title' => __('Sample quote', 'easy-invoice'),
1485 + 'invoice_url' => home_url('/'),
1486 + 'quote_url' => home_url('/'),
1487 + 'payment_url' => home_url('/'),
1488 + 'site_url' => home_url('/'),
1489 + 'admin_url' => admin_url(),
1490 + 'client_email' => 'client@example.com',
1491 + 'client_first_name' => 'Test',
1492 + 'client_last_name' => 'Client',
1493 + 'client_address' => '1 Example Street',
1494 + 'company_phone' => get_option('easy_invoice_company_phone', ''),
1495 + 'company_address' => get_option('easy_invoice_company_address', ''),
1496 + 'company_website' => get_option('easy_invoice_company_website', ''),
1592 1497 ];
1593 -
1498 +
1594 1499 // Ensure all sample data values are strings
1595 1500 foreach ($sample_data as $key => $value) {
1596 1501 $sample_data[$key] = is_string($value) ? $value : (string) $value;
1597 1502 }
1598 -
1503 +
1599 1504 // Replace placeholders in subject and body
1600 1505 foreach ($sample_data as $placeholder => $value) {
1601 1506 $subject = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $subject);
1602 1507 $body = easy_invoice_str_replace('{{' . $placeholder . '}}', $value, $body);
1603 1508 }
1604 -
1509 +
1605 1510 // Test email sending
1606 1511 $email_manager = \EasyInvoice\Services\EmailManager::getInstance();
1607 1512 $result = $email_manager->sendTestTemplateEmail($test_email, $subject, $body);
1608 -
1513 +
1609 1514 if ($result['success']) {
1610 1515 wp_send_json_success($result);
1611 1516 } else {
1612 1517 wp_send_json_error($result);
@@ -1699,9 +1604,9 @@
1699 1604 *
1700 1605 * @return string Quote footer text
1701 1606 */
1702 1607 public static function getQuoteFooterText(): string {
1703 - return get_option('easy_invoice_quote_footer_text', __('Thanks for choosing Easy Invoice', 'easy-invoice'));
1608 + return get_option('easy_invoice_quote_footer_text', __('Thank you for your business.', 'easy-invoice'));
1704 1609 }
1705 1610
1706 1611 /**
1707 1612 * Check if quote accept button should be shown
@@ -1757,9 +1662,9 @@
1757 1662 return get_option('easy_invoice_quote_declined_message', '');
1758 1663 }
1759 1664
1760 1665 // Email Settings Helper Functions
1761 -
1666 +
1762 1667 /**
1763 1668 * Check if invoice available email is enabled
1764 1669 *
1765 1670 * @return bool True if invoice email is enabled
@@ -1782,41 +1687,9 @@
1782 1687 *
1783 1688 * @return string Invoice email body
1784 1689 */
1785 1690 public static function getInvoiceEmailBody(): string {
1786 - return get_option('easy_invoice_invoice_email_body', __('<h2>📄 Your Invoice is Ready</h2>
1787 -
1788 -<p>Dear {{client_name}},</p>
1789 -
1790 -<div class="highlight-box">
1791 - <p><strong>Invoice #{{invoice_number}}</strong><br>
1792 - <span class="amount-highlight">{{total_amount}}</span><br>
1793 - Due Date: <strong>{{due_date}}</strong></p>
1794 -</div>
1795 -
1796 -<p>Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment.</p>
1797 -
1798 -<div class="info-box">
1799 - <p><strong>📋 Payment Details:</strong><br>
1800 - • Invoice Number: {{invoice_number}}<br>
1801 - • Total Amount: {{total_amount}}<br>
1802 - • Due Date: {{due_date}}<br>
1803 - • Payment Terms: {{payment_terms}}</p>
1804 -</div>
1805 -
1806 -<div class="warning-box">
1807 - <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1808 -</div>
1809 -
1810 -<p>If you have any questions about this invoice, please do not hesitate to contact us.</p>
1811 -
1812 -<div class="divider"></div>
1813 -
1814 -<p>Thank you for your business!</p>
1815 -
1816 -<p>Best regards,<br>
1817 -<strong>{{company_name}}</strong><br>
1818 -{{company_email}}</p>', 'easy-invoice'));
1691 + return get_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'));
1819 1692 }
1820 1693
1821 1694 /**
1822 1695 * Check if quote available email is enabled
@@ -1841,41 +1714,9 @@
1841 1714 *
1842 1715 * @return string Quote email body
1843 1716 */
1844 1717 public static function getQuoteEmailBody(): string {
1845 - return get_option('easy_invoice_quote_email_body', __('<h2>📋 Your Quote is Ready</h2>
1846 -
1847 -<p>Dear {{client_name}},</p>
1848 -
1849 -<div class="highlight-box">
1850 - <p><strong>Quote #{{quote_number}}</strong><br>
1851 - <span class="amount-highlight">{{total_amount}}</span><br>
1852 - Valid Until: <strong>{{expiry_date}}</strong></p>
1853 -</div>
1854 -
1855 -<p>We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment.</p>
1856 -
1857 -<div class="info-box">
1858 - <p><strong>📋 Quote Summary:</strong><br>
1859 - • Quote Number: {{quote_number}}<br>
1860 - • Total Amount: {{total_amount}}<br>
1861 - • Valid Until: {{expiry_date}}<br>
1862 - • Terms: {{payment_terms}}</p>
1863 -</div>
1864 -
1865 -<div class="warning-box">
1866 - <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
1867 -</div>
1868 -
1869 -<p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
1870 -
1871 -<div class="divider"></div>
1872 -
1873 -<p>We look forward to working with you!</p>
1874 -
1875 -<p>Best regards,<br>
1876 -<strong>{{company_name}}</strong><br>
1877 -{{company_email}}</p>', 'easy-invoice'));
1718 + return get_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote'));
1878 1719 }
1879 1720
1880 1721 /**
1881 1722 * Check if payment received email is enabled
@@ -1900,51 +1741,15 @@
1900 1741 *
1901 1742 * @return string Payment email body
1902 1743 */
1903 1744 public static function getPaymentEmailBody(): string {
1904 - return get_option('easy_invoice_payment_email_body', __('<h2>✅ Payment Received - Thank You!</h2>
1905 -
1906 -<p>Dear {{client_name}},</p>
1907 -
1908 -<div class="success-box">
1909 - <p><strong>Payment Confirmation</strong><br>
1910 - Invoice #{{invoice_number}}<br>
1911 - <span class="amount-highlight">{{payment_amount}}</span><br>
1912 - Payment Date: <strong>{{payment_date}}</strong><br>
1913 - Payment Method: <strong>{{payment_method}}</strong></p>
1914 -</div>
1915 -
1916 -<p>We have successfully received your payment. Thank you for your prompt payment!</p>
1917 -
1918 -<div class="info-box">
1919 - <p><strong>📊 Payment Details:</strong><br>
1920 - • Invoice Number: {{invoice_number}}<br>
1921 - • Amount Paid: {{payment_amount}}<br>
1922 - • Payment Date: {{payment_date}}<br>
1923 - • Payment Method: {{payment_method}}<br>
1924 - • Transaction ID: {{transaction_id}}</p>
1925 -</div>
1926 -
1927 -<div class="highlight-box">
1928 - <p><strong>🎉 Status: PAID</strong><br>
1929 - Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1930 -</div>
1931 -
1932 -<p>If you have any questions about this payment or need a receipt, please do not hesitate to contact us.</p>
1933 -
1934 -<div class="divider"></div>
1935 -
1936 -<p>Thank you for choosing our services!</p>
1937 -
1938 -<p>Best regards,<br>
1939 -<strong>{{company_name}}</strong><br>
1940 -{{company_email}}</p>', 'easy-invoice'));
1745 + return get_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment'));
1941 1746 }
1942 1747
1943 1748 // ========================================
1944 1749 // TEXT SETTINGS HELPER FUNCTIONS
1945 1750 // ========================================
1946 - //
1751 + //
1947 1752 // USAGE IN TEMPLATES:
1948 1753 // Instead of: echo __('Invoice', 'easy-invoice');
1949 1754 // Use: echo \EasyInvoice\Controllers\SettingsController::getTextInvoice();
1950 1755 //
@@ -1960,11 +1765,74 @@
1960 1765 * @param string $key Text setting key
1961 1766 * @param string $default Default text
1962 1767 * @return string Custom text or default
1963 1768 */
1769 + /**
1770 + * Every document label the site can override, with its saved value.
1771 + *
1772 + * The getText*() methods below are what templates call; this list exists
1773 + * for anything that needs the whole set at once -- the Pro Translations
1774 + * page offers each saved label for translation.
1775 + *
1776 + * @return array<string,array{label:string,default:string,value:string}> keyed by setting key.
1777 + */
1778 + public static function getTextLabels(): array {
1779 + $labels = [
1780 + 'invoice' => __('Invoice', 'easy-invoice'),
1781 + 'invoices' => __('Invoices', 'easy-invoice'),
1782 + 'to' => __('To', 'easy-invoice'),
1783 + 'from' => __('From', 'easy-invoice'),
1784 + 'invoice_number' => __('Invoice Number', 'easy-invoice'),
1785 + 'invoice_date' => __('Invoice Date', 'easy-invoice'),
1786 + 'due_date' => __('Due Date', 'easy-invoice'),
1787 + 'total_due' => __('Total Due', 'easy-invoice'),
1788 + 'qty' => __('Qty', 'easy-invoice'),
1789 + 'service' => __('Service', 'easy-invoice'),
1790 + 'rate_price' => __('Rate', 'easy-invoice'),
1791 + 'adjust' => __('Adjust', 'easy-invoice'),
1792 + 'sub_total' => __('Sub Total', 'easy-invoice'),
1793 + 'total' => __('Total', 'easy-invoice'),
1794 + 'tax' => __('Tax', 'easy-invoice'),
1795 + 'discount' => __('Discount', 'easy-invoice'),
1796 + 'print' => __('Print', 'easy-invoice'),
1797 + 'download_pdf' => __('Download as PDF', 'easy-invoice'),
1798 + 'send_email' => __('Send Email', 'easy-invoice'),
1799 + 'pay_now' => __('Pay Now', 'easy-invoice'),
1800 + 'quote' => __('Quote', 'easy-invoice'),
1801 + 'quote_number' => __('Quote Number', 'easy-invoice'),
1802 + 'quote_date' => __('Quote Date', 'easy-invoice'),
1803 + 'valid_until' => __('Valid Until Date', 'easy-invoice'),
1804 + 'accept_quote' => __('Accept Quote', 'easy-invoice'),
1805 + 'decline_quote' => __('Decline Quote', 'easy-invoice'),
1806 + 'decline_reason' => __('Reason for declining', 'easy-invoice'),
1807 + ];
1808 + $out = [];
1809 + foreach ($labels as $key => $default) {
1810 + $out[$key] = [
1811 + 'label' => ucfirst(str_replace('_', ' ', $key)),
1812 + 'default' => $default,
1813 + 'value' => (string) get_option('easy_invoice_text_' . $key, $default),
1814 + ];
1815 + }
1816 + return $out;
1817 + }
1818 +
1964 1819 public static function getTextSetting(string $key, string $default): string {
1965 1820 $option_key = 'easy_invoice_text_' . $key;
1966 - return get_option($option_key, $default);
1821 + $value = (string) get_option($option_key, $default);
1822 +
1823 + /**
1824 + * Filter a document label after the saved override is applied.
1825 + *
1826 + * Saved labels are literal text and skip gettext, so anything that
1827 + * renders a document in another language (Pro's Client Language) uses
1828 + * this to run them through the active locale.
1829 + *
1830 + * @param string $value Label to show.
1831 + * @param string $key Setting key, e.g. 'total'.
1832 + * @param string $default Translated default.
1833 + */
1834 + return (string) apply_filters('easy_invoice_text_setting', $value, $key, $default);
1967 1835 }
1968 1836
1969 1837 // Invoice Text Settings
1970 1838 public static function getTextInvoice(): string {
@@ -2018,8 +1886,13 @@
2018 1886 public static function getTextTotal(): string {
2019 1887 return self::getTextSetting('total', __('Total', 'easy-invoice'));
2020 1888 }
2021 1889
1890 + /** Heading of the line-item amount column, separate from the grand total. */
1891 + public static function getTextLineTotal(): string {
1892 + return self::getTextSetting('line_total', __('Total', 'easy-invoice'));
1893 + }
1894 +
2022 1895 public static function getTextTax(): string {
2023 1896 return self::getTextSetting('tax', __('Tax', 'easy-invoice'));
2024 1897 }
2025 1898
@@ -2085,9 +1958,9 @@
2085 1958 public static function getDateFormatString($format_identifier = null): string {
2086 1959 if ($format_identifier === null) {
2087 1960 $format_identifier = get_option('easy_invoice_date_format', 'us');
2088 1961 }
2089 -
1962 +
2090 1963 switch ($format_identifier) {
2091 1964 case 'uk':
2092 1965 return 'd/m/Y';
2093 1966 case 'iso':
@@ -2122,9 +1995,9 @@
2122 1995 /**
2123 1996 * AJAX handler for regenerating invoice numbers
2124 1997 */
2125 1998 public function ajaxRegenerateInvoiceNumbers() {
2126 -
1999 +
2127 2000 // Verify nonce
2128 2001 $nonce = $_POST['nonce'] ?? $_POST['easy_invoice_settings_nonce'] ?? '';
2129 2002 if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) {
2130 2003 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
@@ -2138,12 +2011,12 @@
2138 2011 try {
2139 2012 // Get the current next invoice number
2140 2013 $current_next_number = intval(get_option('easy_invoice_next_invoice_number', 1));
2141 2014 $prefix = get_option('easy_invoice_invoice_prefix', 'EIIN_');
2142 -
2015 +
2143 2016 // Start regeneration from the current next number
2144 2017 $starting_number = $current_next_number;
2145 -
2018 +
2146 2019 // Get all invoices ordered by creation date
2147 2020 $invoices = get_posts([
2148 2021 'post_type' => 'easy_invoice',
2149 2022 'post_status' => 'publish',
@@ -2164,12 +2037,12 @@
2164 2037
2165 2038 foreach ($invoices as $invoice) {
2166 2039 // Generate new invoice number
2167 2040 $new_invoice_number = $prefix . str_pad($current_number, 6, '0', STR_PAD_LEFT);
2168 -
2041 +
2169 2042 // Update the invoice number
2170 2043 update_post_meta($invoice->ID, '_easy_invoice_number', $new_invoice_number);
2171 -
2044 +
2172 2045 $regenerated_count++;
2173 2046 $current_number++;
2174 2047 }
2175 2048
@@ -2176,8 +2049,9 @@
2176 2049 // Update the next invoice number counter to continue from the last regenerated number + 1
2177 2050 update_option('easy_invoice_next_invoice_number', $current_number);
2178 2051
2179 2052 wp_send_json_success([
2053 + /* translators: %d: number regenerated. */
2180 2054 'message' => sprintf(__('Successfully regenerated %d invoice numbers', 'easy-invoice'), $regenerated_count),
2181 2055 'regenerated_count' => $regenerated_count,
2182 2056 'next_number' => $current_number
2183 2057 ]);
@@ -2190,9 +2064,9 @@
2190 2064 /**
2191 2065 * AJAX handler for regenerating quote numbers
2192 2066 */
2193 2067 public function ajaxRegenerateQuoteNumbers() {
2194 -
2068 +
2195 2069 // Verify nonce
2196 2070 $nonce = $_POST['nonce'] ?? $_POST['easy_invoice_settings_nonce'] ?? '';
2197 2071 if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) {
2198 2072 wp_send_json_error(['message' => __('Security check failed', 'easy-invoice')]);
@@ -2235,8 +2109,9 @@
2235 2109
2236 2110 update_option('easy_invoice_next_quote_number', $current_number);
2237 2111
2238 2112 wp_send_json_success([
2113 + /* translators: %d: number regenerated. */
2239 2114 'message' => sprintf(__('Successfully regenerated %d quote numbers', 'easy-invoice'), $regenerated_count),
2240 2115 'regenerated_count' => $regenerated_count,
2241 2116 'next_number' => $current_number
2242 2117 ]);
@@ -2244,5 +2119,5 @@
2244 2119 } catch (Exception $e) {
2245 2120 wp_send_json_error(['message' => __('Failed to regenerate quote numbers', 'easy-invoice')]);
2246 2121 }
2247 2122 }
2248 -}
2123 +}