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