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