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