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