| @@ -5,8 +5,11 @@ | ||
| 5 | 5 | * @package EasyInvoice |
| 6 | 6 | * @since 1.0.0 |
| 7 | 7 | */ |
| 8 | 8 | |
| 9 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 10 | + exit; | |
| 11 | +} | |
| 9 | 12 | // Only enqueue scripts if not loaded via AJAX |
| 10 | 13 | if (!wp_doing_ajax()) { |
| 11 | 14 | // Enqueue CSS and JS files |
| 12 | 15 | wp_enqueue_style('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/css/invoice-form.css', array(), EASY_INVOICE_VERSION); |
| @@ -238,11 +241,11 @@ | ||
| 238 | 241 | <div class="tab-content <?php echo $tab['active'] ? 'active' : 'hidden'; ?> px-6 py-5" id="<?php echo esc_attr($tab['id']); ?>"> |
| 239 | 242 | <?php if ($tab['id'] === 'quote-tab'): ?> |
| 240 | 243 | <div class="mb-8 pb-6 border-b border-gray-200"> |
| 241 | 244 | <div class="flex justify-between items-center mb-4"> |
| 242 | - <h3 class="text-lg font-medium text-gray-900"><?php _e('Quote Template', 'easy-invoice'); ?></h3> | |
| 245 | + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Quote Template', 'easy-invoice'); ?></h3> | |
| 243 | 246 | <button type="button" id="browse-all-templates" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center"> |
| 244 | - <span class="mr-1"><?php _e('Browse Quote Templates', 'easy-invoice'); ?></span> | |
| 247 | + <span class="mr-1"><?php esc_html_e('Browse Quote Templates', 'easy-invoice'); ?></span> | |
| 245 | 248 | <i class="fas fa-chevron-right"></i> |
| 246 | 249 | </button> |
| 247 | 250 | </div> |
| 248 | 251 | |
| @@ -312,10 +315,10 @@ | ||
| 312 | 315 | if ($is_free_version && $is_premium) { |
| 313 | 316 | $premium_classes = 'premium-template-blurred'; |
| 314 | 317 | } |
| 315 | 318 | ?> |
| 316 | - <div class="template-card <?php echo $selected_class . ' ' . $premium_classes; ?>" data-template-id="<?php echo esc_attr($template_id); ?>" data-is-premium="<?php echo $is_premium ? 'true' : 'false'; ?>"> | |
| 317 | - <input type="radio" id="template-<?php echo esc_attr($template_id); ?>" name="quote_template" value="<?php echo esc_attr($template_id); ?>" class="template-radio" <?php echo $checked; ?> <?php echo ($is_free_version && $is_premium) ? 'disabled' : ''; ?>> | |
| 319 | + <div class="template-card <?php echo esc_attr($selected_class . ' ' . $premium_classes); ?>" data-template-id="<?php echo esc_attr($template_id); ?>" data-is-premium="<?php echo esc_attr($is_premium ? 'true' : 'false'); ?>"> | |
| 320 | + <input type="radio" id="template-<?php /* translators: %s: template name. */ echo esc_attr($template_id); ?>" name="quote_template" value="<?php echo esc_attr($template_id); ?>" aria-label="<?php echo esc_attr(sprintf(__('Use the %s quote template', 'easy-invoice'), $template['name'])); ?>" class="template-radio" <?php echo esc_html($checked); ?> <?php echo esc_attr(($is_free_version && $is_premium) ? 'disabled' : ''); ?>> | |
| 318 | 321 | <div class="p-4 border rounded-lg"> |
| 319 | 322 | <div class="flex items-center mb-2"> |
| 320 | 323 | <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i> |
| 321 | 324 | <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span> |
| @@ -341,9 +344,9 @@ | ||
| 341 | 344 | // Skip fields that have a section (like notes) - they're rendered separately |
| 342 | 345 | // Also skip the template field as it's rendered manually in the template selection section |
| 343 | 346 | if (!isset($field['section']) && $field['name'] !== 'quote_template') { |
| 344 | 347 | |
| 345 | - echo $quote_form_manager->renderField($field, $quote); | |
| 348 | + echo $quote_form_manager->renderField($field, $quote); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits. | |
| 346 | 349 | } |
| 347 | 350 | endforeach; |
| 348 | 351 | ?> |
| 349 | 352 | </div> |
| @@ -355,9 +358,9 @@ | ||
| 355 | 358 | <i class="fas fa-info-circle text-blue-400"></i> |
| 356 | 359 | </div> |
| 357 | 360 | <div class="ml-3"> |
| 358 | 361 | <p class="text-sm text-blue-700"> |
| 359 | - <?php _e('Quote number will be automatically generated when you save the quote.', 'easy-invoice'); ?> | |
| 362 | + <?php esc_html_e('Quote number will be automatically generated when you save the quote.', 'easy-invoice'); ?> | |
| 360 | 363 | </p> |
| 361 | 364 | </div> |
| 362 | 365 | </div> |
| 363 | 366 | </div> |
| @@ -363,15 +366,15 @@ | ||
| 363 | 366 | </div> |
| 364 | 367 | <?php endif; ?> |
| 365 | 368 | |
| 366 | 369 | <div class="mt-8 border-t border-gray-200 pt-6"> |
| 367 | - <h3 class="text-lg font-medium text-gray-900"><?php _e('Notes & Terms', 'easy-invoice'); ?></h3> | |
| 370 | + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Notes & Terms', 'easy-invoice'); ?></h3> | |
| 368 | 371 | <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6"> |
| 369 | 372 | <?php |
| 370 | 373 | // Only render notes and terms fields, not all quote tab fields |
| 371 | 374 | $notes_fields = $quote_form_manager->getFieldsBySection('quote-tab', 'notes'); |
| 372 | 375 | foreach ($notes_fields as $field): |
| 373 | - echo $quote_form_manager->renderField($field, $quote); | |
| 376 | + echo $quote_form_manager->renderField($field, $quote); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits. | |
| 374 | 377 | endforeach; |
| 375 | 378 | ?> |
| 376 | 379 | </div> |
| 377 | 380 | </div> |
| @@ -377,12 +380,12 @@ | ||
| 377 | 380 | </div> |
| 378 | 381 | |
| 379 | 382 | <?php elseif ($tab['id'] === 'items-tab'): ?> |
| 380 | 383 | <div class="flex justify-between items-center mb-6"> |
| 381 | - <h2 class="text-lg font-medium text-gray-900"><?php _e('Quote Items', 'easy-invoice'); ?></h2> | |
| 384 | + <h2 class="text-lg font-medium text-gray-900"><?php esc_html_e('Quote Items', 'easy-invoice'); ?></h2> | |
| 382 | 385 | <button type="button" id="collapse-all-items" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center"> |
| 383 | 386 | <i class="fas fa-chevron-down mr-1"></i> |
| 384 | - <span><?php _e('Collapse All', 'easy-invoice'); ?></span> | |
| 387 | + <span><?php esc_html_e('Collapse All', 'easy-invoice'); ?></span> | |
| 385 | 388 | </button> |
| 386 | 389 | </div> |
| 387 | 390 | |
| 388 | 391 | <div class="space-y-4 quote-items-container"> |
| @@ -391,9 +394,9 @@ | ||
| 391 | 394 | if ($quote && $quote->getItems()) : |
| 392 | 395 | $index = 0; |
| 393 | 396 | foreach ($quote->getItems() as $item) : |
| 394 | 397 | // Generate a unique ID for the item |
| 395 | - $item_id = 'item_' . time() . '_' . $index . '_' . rand(1000, 9999); | |
| 398 | + $item_id = 'item_' . time() . '_' . $index . '_' . wp_rand(1000, 9999); | |
| 396 | 399 | |
| 397 | 400 | // Get display title for header using dynamic field access |
| 398 | 401 | $title = $item->getName(); |
| 399 | 402 | |
| @@ -404,12 +407,13 @@ | ||
| 404 | 407 | $title = $item_data['title']; |
| 405 | 408 | } |
| 406 | 409 | } |
| 407 | 410 | |
| 411 | + /* translators: %d: title. */ | |
| 408 | 412 | $display_title = !empty($title) ? $title : sprintf(__('Item #%d', 'easy-invoice'), $index + 1); |
| 409 | 413 | $short_title = strlen($display_title) > 30 ? substr($display_title, 0, 30) . '...' : $display_title; |
| 410 | 414 | ?> |
| 411 | - <div class="quote-item p-6 rounded-xl shadow-sm border border-blue-100 hover:shadow-md transition-all duration-200 mb-6" style="background-color: #f8fbff;" id="<?php echo $item_id; ?>"> | |
| 415 | + <div class="quote-item p-6 rounded-xl shadow-sm border border-blue-100 hover:shadow-md transition-all duration-200 mb-6" style="background-color: #f8fbff;" id="<?php echo (int) $item_id; ?>"> | |
| 412 | 416 | <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100"> |
| 413 | 417 | <div class="flex items-center"> |
| 414 | 418 | <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors"> |
| 415 | 419 | <i class="fas fa-chevron-down text-sm"></i> |
| @@ -415,9 +419,9 @@ | ||
| 415 | 419 | <i class="fas fa-chevron-down text-sm"></i> |
| 416 | 420 | </button> |
| 417 | 421 | <h3 class="text-base font-normal text-gray-800 flex-1"> |
| 418 | 422 | <?php |
| 419 | - echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice'); | |
| 423 | + echo !empty($title) ? esc_html($title) : esc_html__('New Item', 'easy-invoice'); | |
| 420 | 424 | ?> |
| 421 | 425 | </h3> |
| 422 | 426 | </div> |
| 423 | 427 | <div class="flex items-center gap-3"> |
| @@ -424,9 +428,9 @@ | ||
| 424 | 428 | <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full"> |
| 425 | 429 | <span class="quantity-summary"><?php echo esc_html($item->getQuantity()); ?></span> × $<span class="price-summary"><?php echo esc_html(number_format(floatval($item->getPrice()), 2)); ?></span> |
| 426 | 430 | = $<span class="total-summary font-medium"><?php echo esc_html(number_format($item->getAmount(), 2)); ?></span> |
| 427 | 431 | </div> |
| 428 | - <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Fill this item with sample data', 'easy-invoice'); ?>"> | |
| 432 | + <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Fill this item with sample data', 'easy-invoice')); ?>"> | |
| 429 | 433 | <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block"> |
| 430 | 434 | <path d="M2 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H2zm0 1h16v14H2V3z"/> |
| 431 | 435 | <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/> |
| 432 | 436 | <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/> |
| @@ -433,14 +437,14 @@ | ||
| 433 | 437 | <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/> |
| 434 | 438 | </svg> |
| 435 | 439 | </button> |
| 436 | 440 | <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?> |
| 437 | - <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php _e('Select from Item Library', 'easy-invoice'); ?>"> | |
| 441 | + <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Select from Item Library', 'easy-invoice')); ?>"> | |
| 438 | 442 | <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block"> |
| 439 | 443 | <path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/> |
| 440 | 444 | </svg> |
| 441 | 445 | </button> |
| 442 | - <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Save to Item Library', 'easy-invoice'); ?>" data-item-index="<?php echo $index; ?>" data-nonce="<?php echo wp_create_nonce('easy_invoice_pro_item_library'); ?>"> | |
| 446 | + <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Save to Item Library', 'easy-invoice')); ?>" data-item-index="<?php echo esc_attr($index); ?>" data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_pro_item_library')); ?>"> | |
| 443 | 447 | <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block"> |
| 444 | 448 | <path d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"/> |
| 445 | 449 | </svg> |
| 446 | 450 | </button> |
| @@ -445,14 +449,15 @@ | ||
| 445 | 449 | </svg> |
| 446 | 450 | </button> |
| 447 | 451 | <?php endif; ?> |
| 448 | 452 | <button type="button" class="remove-item text-gray-400 hover:text-red-600 hover:bg-red-50 p-2 rounded-lg transition-all duration-200 text-sm font-medium"> |
| 449 | - <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?> | |
| 453 | + <i class="fas fa-trash-alt mr-1"></i> <?php esc_html_e('Remove', 'easy-invoice'); ?> | |
| 450 | 454 | </button> |
| 451 | 455 | </div> |
| 452 | 456 | </div> |
| 453 | 457 | <div class="item-content"> |
| 454 | - <?php echo $quote_form_manager->generateExistingItemHtml($item, $index); ?> | |
| 458 | + <?php echo $quote_form_manager->generateExistingItemHtml($item, $index); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- ItemFieldRenderer escapes every value it emits. | |
| 459 | +?> | |
| 455 | 460 | </div> |
| 456 | 461 | </div> |
| 457 | 462 | <?php |
| 458 | 463 | $index++; |
| @@ -488,9 +493,9 @@ | ||
| 488 | 493 | <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full"> |
| 489 | 494 | <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span> |
| 490 | 495 | = $<span class="total-summary font-medium">0.00</span> |
| 491 | 496 | </div> |
| 492 | - <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Fill this item with sample data', 'easy-invoice'); ?>"> | |
| 497 | + <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Fill this item with sample data', 'easy-invoice')); ?>"> | |
| 493 | 498 | <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block"> |
| 494 | 499 | <path d="M2 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H2zm0 1h16v14H2V3z"/> |
| 495 | 500 | <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/> |
| 496 | 501 | <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/> |
| @@ -497,14 +502,14 @@ | ||
| 497 | 502 | <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/> |
| 498 | 503 | </svg> |
| 499 | 504 | </button> |
| 500 | 505 | <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?> |
| 501 | - <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php _e('Select from Item Library', 'easy-invoice'); ?>"> | |
| 506 | + <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Select from Item Library', 'easy-invoice')); ?>"> | |
| 502 | 507 | <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block"> |
| 503 | 508 | <path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/> |
| 504 | 509 | </svg> |
| 505 | 510 | </button> |
| 506 | - <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Save to Item Library', 'easy-invoice'); ?>" data-item-index="0" data-nonce="<?php echo wp_create_nonce('easy_invoice_pro_item_library'); ?>"> | |
| 511 | + <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Save to Item Library', 'easy-invoice')); ?>" data-item-index="0" data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_pro_item_library')); ?>"> | |
| 507 | 512 | <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block"> |
| 508 | 513 | <path d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"/> |
| 509 | 514 | </svg> |
| 510 | 515 | </button> |
| @@ -509,14 +514,14 @@ | ||
| 509 | 514 | </svg> |
| 510 | 515 | </button> |
| 511 | 516 | <?php endif; ?> |
| 512 | 517 | <button type="button" class="remove-item text-gray-400 hover:text-red-600 hover:bg-red-50 p-2 rounded-lg transition-all duration-200 text-sm font-medium"> |
| 513 | - <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?> | |
| 518 | + <i class="fas fa-trash-alt mr-1"></i> <?php esc_html_e('Remove', 'easy-invoice'); ?> | |
| 514 | 519 | </button> |
| 515 | 520 | </div> |
| 516 | 521 | </div> |
| 517 | 522 | <div class="item-content"> |
| 518 | - <?php echo $quote_form_manager->generateItemTemplateHtml(); ?> | |
| 523 | + <?php echo $quote_form_manager->generateItemTemplateHtml(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- form markup built by ItemFieldRenderer, which escapes every value. ?> | |
| 519 | 524 | </div> |
| 520 | 525 | </div> |
| 521 | 526 | <?php endif; ?> |
| 522 | 527 | </div> |
| @@ -527,10 +532,10 @@ | ||
| 527 | 532 | <div class="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center mr-3 group-hover:bg-indigo-200 transition-colors"> |
| 528 | 533 | <i class="fas fa-plus text-indigo-600 text-lg"></i> |
| 529 | 534 | </div> |
| 530 | 535 | <div class="text-left"> |
| 531 | - <span class="font-semibold text-base"><?php _e('Add Another Item', 'easy-invoice'); ?></span> | |
| 532 | - <p class="text-sm text-indigo-600 mt-0.5"><?php _e('Click to add a new line item', 'easy-invoice'); ?></p> | |
| 536 | + <span class="font-semibold text-base"><?php esc_html_e('Add Another Item', 'easy-invoice'); ?></span> | |
| 537 | + <p class="text-sm text-indigo-600 mt-0.5"><?php esc_html_e('Click to add a new line item', 'easy-invoice'); ?></p> | |
| 533 | 538 | </div> |
| 534 | 539 | </div> |
| 535 | 540 | </button> |
| 536 | 541 | </div> |
| @@ -536,9 +541,9 @@ | ||
| 536 | 541 | </div> |
| 537 | 542 | |
| 538 | 543 | <?php elseif ($tab['id'] === 'client-tab'): ?> |
| 539 | 544 | <div class="mb-6"> |
| 540 | - <label class="block text-sm font-medium text-gray-700 mb-3"><?php _e('Select Existing Client', 'easy-invoice'); ?></label> | |
| 545 | + <label class="block text-sm font-medium text-gray-700 mb-3"><?php esc_html_e('Select Existing Client', 'easy-invoice'); ?></label> | |
| 541 | 546 | |
| 542 | 547 | <div class="relative"> |
| 543 | 548 | <div class="flex items-center space-x-3"> |
| 544 | 549 | <div class="relative flex-grow"> |
| @@ -548,15 +553,15 @@ | ||
| 548 | 553 | <input |
| 549 | 554 | type="text" |
| 550 | 555 | id="client-search-input" |
| 551 | 556 | class="block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm py-2 px-3 pl-10" |
| 552 | - placeholder="<?php _e('Search clients by name, email, or company...', 'easy-invoice'); ?>" | |
| 557 | + placeholder="<?php echo esc_js(__('Search clients by name, email, or company...', 'easy-invoice')); ?>" | |
| 553 | 558 | autocomplete="off" |
| 554 | 559 | > |
| 555 | 560 | </div> |
| 556 | 561 | <button type="button" id="add-new-client-btn" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"> |
| 557 | 562 | <i class="fas fa-plus -ml-1 mr-2"></i> |
| 558 | - <?php _e('New Client', 'easy-invoice'); ?> | |
| 563 | + <?php esc_html_e('New Client', 'easy-invoice'); ?> | |
| 559 | 564 | </button> |
| 560 | 565 | </div> |
| 561 | 566 | |
| 562 | 567 | <div id="client-search-results" class="hidden absolute z-50 w-full mt-2 bg-white border border-gray-200 rounded-lg shadow-xl max-h-80 overflow-y-auto"> |
| @@ -561,13 +566,13 @@ | ||
| 561 | 566 | |
| 562 | 567 | <div id="client-search-results" class="hidden absolute z-50 w-full mt-2 bg-white border border-gray-200 rounded-lg shadow-xl max-h-80 overflow-y-auto"> |
| 563 | 568 | <div id="client-search-loading" class="hidden p-6 text-center text-gray-500"> |
| 564 | 569 | <i class="fas fa-spinner fa-spin mr-2"></i> |
| 565 | - <?php _e('Searching...', 'easy-invoice'); ?> | |
| 570 | + <?php esc_html_e('Searching...', 'easy-invoice'); ?> | |
| 566 | 571 | </div> |
| 567 | 572 | <div id="client-search-empty" class="hidden p-6 text-center text-gray-500"> |
| 568 | 573 | <i class="fas fa-search mr-2"></i> |
| 569 | - <?php _e('No clients found', 'easy-invoice'); ?> | |
| 574 | + <?php esc_html_e('No clients found', 'easy-invoice'); ?> | |
| 570 | 575 | </div> |
| 571 | 576 | <div id="client-search-list" class="py-2"> |
| 572 | 577 | </div> |
| 573 | 578 | </div> |
| @@ -618,10 +623,15 @@ | ||
| 618 | 623 | </div> |
| 619 | 624 | </div> |
| 620 | 625 | |
| 621 | 626 | <select id="select-client" class="hidden"> |
| 622 | - <option value=""><?php _e('-- Select a client --', 'easy-invoice'); ?></option> | |
| 627 | + <option value=""><?php esc_html_e('-- Select a client --', 'easy-invoice'); ?></option> | |
| 623 | 628 | <?php |
| 629 | + // Only the quote's own client is mirrored here; the picker searches over AJAX. | |
| 630 | + if (empty($clients) && $quote && method_exists($quote, 'getClientId') && (int) $quote->getClientId() > 0) { | |
| 631 | + $ei_selected_client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find((int) $quote->getClientId()); | |
| 632 | + if ($ei_selected_client) { $clients = [ $ei_selected_client ]; } | |
| 633 | + } | |
| 624 | 634 | // Output client options |
| 625 | 635 | if (!empty($clients)) { |
| 626 | 636 | foreach ($clients as $client) { |
| 627 | 637 | // Get the WordPress user data directly |
| @@ -645,9 +655,9 @@ | ||
| 645 | 655 | $selected = $quote && $quote->getClientId() == $client->getId() ? 'selected' : ''; |
| 646 | 656 | printf( |
| 647 | 657 | '<option value="%s" %s data-name="%s" data-email="%s">%s (%s)</option>', |
| 648 | 658 | esc_attr($client->getId()), |
| 649 | - $selected, | |
| 659 | + $selected, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output of selected( ..., false ). | |
| 650 | 660 | esc_attr($client_name), |
| 651 | 661 | esc_attr($client_email), |
| 652 | 662 | esc_html($client_name), |
| 653 | 663 | esc_html($client_email) |
| @@ -665,16 +675,16 @@ | ||
| 665 | 675 | <div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm"> |
| 666 | 676 | <div class="flex items-center justify-between pb-4 border-b border-gray-200 mb-4"> |
| 667 | 677 | <h4 class="text-lg font-semibold text-gray-800 flex items-center"> |
| 668 | 678 | <i class="fas fa-user-check text-indigo-500 mr-3"></i> |
| 669 | - <span><?php _e('Client Information', 'easy-invoice'); ?></span> | |
| 679 | + <span><?php esc_html_e('Client Information', 'easy-invoice'); ?></span> | |
| 670 | 680 | </h4> |
| 671 | - <a href="<?php echo admin_url('admin.php?page=easy-invoice-client-edit&client_id=' . ($quote && $quote->getClientId() ? $quote->getClientId() : '')); ?>" | |
| 681 | + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-client-edit&client_id=' . ($quote && $quote->getClientId() ? $quote->getClientId() : ''))); ?>" | |
| 672 | 682 | id="edit-selected-client" |
| 673 | - class="text-sm text-indigo-600 hover:text-indigo-800 font-medium" | |
| 683 | + class="text-sm text-indigo-600 hover:text-indigo-800 font-medium<?php echo ($quote && $quote->getClientId()) ? '' : ' hidden'; ?>" | |
| 674 | 684 | target="_blank"> |
| 675 | 685 | <i class="fas fa-pen mr-1"></i> |
| 676 | - <?php _e('Edit', 'easy-invoice'); ?> | |
| 686 | + <?php esc_html_e('Edit', 'easy-invoice'); ?> | |
| 677 | 687 | </a> |
| 678 | 688 | </div> |
| 679 | 689 | |
| 680 | 690 | <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2"> |
| @@ -680,9 +690,9 @@ | ||
| 680 | 690 | <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2"> |
| 681 | 691 | <div class="flex items-start group"> |
| 682 | 692 | <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-user-tie text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div> |
| 683 | 693 | <div class="ml-3"> |
| 684 | - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Name', 'easy-invoice'); ?></label> | |
| 694 | + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Name', 'easy-invoice'); ?></label> | |
| 685 | 695 | <p id="display-client-name" class="text-sm font-semibold text-gray-800 mt-1"> |
| 686 | 696 | <?php |
| 687 | 697 | if ($quote && $quote->getClientId()) { |
| 688 | 698 | $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId()); |
| @@ -701,9 +711,9 @@ | ||
| 701 | 711 | |
| 702 | 712 | <div class="flex items-start group"> |
| 703 | 713 | <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-building text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div> |
| 704 | 714 | <div class="ml-3"> |
| 705 | - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Company', 'easy-invoice'); ?></label> | |
| 715 | + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Company', 'easy-invoice'); ?></label> | |
| 706 | 716 | <p id="display-client-company" class="text-sm font-semibold text-gray-800 mt-1"> |
| 707 | 717 | <?php |
| 708 | 718 | if ($quote && $quote->getClientId()) { |
| 709 | 719 | $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId()); |
| @@ -722,9 +732,9 @@ | ||
| 722 | 732 | |
| 723 | 733 | <div class="flex items-start group"> |
| 724 | 734 | <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-envelope text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div> |
| 725 | 735 | <div class="ml-3"> |
| 726 | - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Email', 'easy-invoice'); ?></label> | |
| 736 | + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Email', 'easy-invoice'); ?></label> | |
| 727 | 737 | <p id="display-client-email" class="text-sm text-gray-800 mt-1 break-all"> |
| 728 | 738 | <?php |
| 729 | 739 | if ($quote && $quote->getClientId()) { |
| 730 | 740 | $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId()); |
| @@ -743,9 +753,9 @@ | ||
| 743 | 753 | |
| 744 | 754 | <div class="flex items-start group"> |
| 745 | 755 | <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-phone-alt text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div> |
| 746 | 756 | <div class="ml-3"> |
| 747 | - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Phone', 'easy-invoice'); ?></label> | |
| 757 | + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Phone', 'easy-invoice'); ?></label> | |
| 748 | 758 | <p id="display-client-phone" class="text-sm text-gray-800 mt-1"> |
| 749 | 759 | <?php |
| 750 | 760 | if ($quote && $quote->getClientId()) { |
| 751 | 761 | $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId()); |
| @@ -764,9 +774,9 @@ | ||
| 764 | 774 | |
| 765 | 775 | <div class="flex items-start group md:col-span-2"> |
| 766 | 776 | <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-globe-americas text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div> |
| 767 | 777 | <div class="ml-3"> |
| 768 | - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Website', 'easy-invoice'); ?></label> | |
| 778 | + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Website', 'easy-invoice'); ?></label> | |
| 769 | 779 | <p id="display-client-website" class="text-sm text-gray-800 mt-1"> |
| 770 | 780 | <?php |
| 771 | 781 | if ($quote && $quote->getClientId()) { |
| 772 | 782 | $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId()); |
| @@ -785,9 +795,9 @@ | ||
| 785 | 795 | |
| 786 | 796 | <div class="flex items-start group md:col-span-2"> |
| 787 | 797 | <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-map-marker-alt text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div> |
| 788 | 798 | <div class="ml-3"> |
| 789 | - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Address', 'easy-invoice'); ?></label> | |
| 799 | + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Address', 'easy-invoice'); ?></label> | |
| 790 | 800 | <p id="display-client-address" class="text-sm text-gray-800 mt-1 whitespace-pre-wrap"> |
| 791 | 801 | <?php |
| 792 | 802 | if ($quote && $quote->getClientId()) { |
| 793 | 803 | $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($quote->getClientId()); |
| @@ -807,15 +817,15 @@ | ||
| 807 | 817 | </div> |
| 808 | 818 | </div> |
| 809 | 819 | |
| 810 | 820 | <div id="no-client-message" class="mt-4 text-center py-6 border-2 border-dashed border-gray-300 rounded-lg <?php echo $quote && $quote->getClientId() ? 'hidden' : ''; ?>"> |
| 811 | - <p class="text-gray-600"><?php _e('Select a client from the dropdown above to view their information.', 'easy-invoice'); ?></p> | |
| 821 | + <p class="text-gray-600"><?php esc_html_e('Select a client from the dropdown above to view their information.', 'easy-invoice'); ?></p> | |
| 812 | 822 | </div> |
| 813 | 823 | </div> |
| 814 | 824 | |
| 815 | 825 | <?php elseif ($tab['id'] === 'discounts-taxes-tab'): ?> |
| 816 | 826 | <div> |
| 817 | - <h3 class="text-lg font-medium text-gray-900"><?php _e('Discount & Tax Settings', 'easy-invoice'); ?></h3> | |
| 827 | + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Discount & Tax Settings', 'easy-invoice'); ?></h3> | |
| 818 | 828 | |
| 819 | 829 | <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6"> |
| 820 | 830 | <?php |
| 821 | 831 | // Get all discount and tax fields |
| @@ -829,9 +839,9 @@ | ||
| 829 | 839 | return ($a['order'] ?? 0) - ($b['order'] ?? 0); |
| 830 | 840 | }); |
| 831 | 841 | |
| 832 | 842 | foreach ($all_fields as $field): |
| 833 | - echo $quote_form_manager->renderField($field, $quote); | |
| 843 | + echo $quote_form_manager->renderField($field, $quote); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits. | |
| 834 | 844 | endforeach; |
| 835 | 845 | ?> |
| 836 | 846 | </div> |
| 837 | 847 | </div> |
| @@ -837,25 +847,25 @@ | ||
| 837 | 847 | </div> |
| 838 | 848 | |
| 839 | 849 | <?php elseif ($tab['id'] === 'payments-tab'): ?> |
| 840 | 850 | <div> |
| 841 | - <h3 class="text-lg font-medium text-gray-900"><?php _e('Payment Information', 'easy-invoice'); ?></h3> | |
| 851 | + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Payment Information', 'easy-invoice'); ?></h3> | |
| 842 | 852 | <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6"> |
| 843 | 853 | <?php |
| 844 | 854 | $payment_fields = $quote_form_manager->getFieldsBySection('payments-tab', 'payment'); |
| 845 | 855 | foreach ($payment_fields as $field): |
| 846 | - echo $quote_form_manager->renderField($field, $quote); | |
| 856 | + echo $quote_form_manager->renderField($field, $quote); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits. | |
| 847 | 857 | endforeach; |
| 848 | 858 | ?> |
| 849 | 859 | </div> |
| 850 | 860 | |
| 851 | 861 | <div class="mt-8 border-t border-gray-200 pt-6"> |
| 852 | - <h3 class="text-lg font-medium text-gray-900"><?php _e('Currency', 'easy-invoice'); ?></h3> | |
| 862 | + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Currency', 'easy-invoice'); ?></h3> | |
| 853 | 863 | <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6"> |
| 854 | 864 | <?php |
| 855 | 865 | $currency_fields = $quote_form_manager->getFieldsBySection('payments-tab', 'currency'); |
| 856 | 866 | foreach ($currency_fields as $field): |
| 857 | - echo $quote_form_manager->renderField($field, $quote); | |
| 867 | + echo $quote_form_manager->renderField($field, $quote); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits. | |
| 858 | 868 | endforeach; |
| 859 | 869 | ?> |
| 860 | 870 | </div> |
| 861 | 871 | </div> |
| @@ -868,11 +878,11 @@ | ||
| 868 | 878 | |
| 869 | 879 | <div id="template-gallery-modal" style="display: none;" class="fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50 flex items-center justify-center"> |
| 870 | 880 | <div class="mx-auto p-6 border w-full md:w-4/5 max-w-4xl shadow-lg rounded-md bg-white max-h-[80vh] overflow-y-auto"> |
| 871 | 881 | <div class="flex justify-between items-center mb-4"> |
| 872 | - <h3 class="text-xl font-medium text-gray-900"><?php _e('Browse Quote Templates', 'easy-invoice'); ?></h3> | |
| 882 | + <h3 class="text-xl font-medium text-gray-900"><?php esc_html_e('Browse Quote Templates', 'easy-invoice'); ?></h3> | |
| 873 | 883 | <button type="button" class="close-gallery-modal text-gray-400 hover:text-gray-500 focus:outline-none"> |
| 874 | - <span class="sr-only"><?php _e('Close', 'easy-invoice'); ?></span> | |
| 884 | + <span class="sr-only"><?php esc_html_e('Close', 'easy-invoice'); ?></span> | |
| 875 | 885 | <i class="fas fa-times text-xl"></i> |
| 876 | 886 | </button> |
| 877 | 887 | </div> |
| 878 | 888 | |
| @@ -882,9 +892,9 @@ | ||
| 882 | 892 | foreach ($all_templates as $template_id => $template) : |
| 883 | 893 | $is_current = ($current_template === $template_id); |
| 884 | 894 | $selected_class = $is_current ? 'selected' : ''; |
| 885 | 895 | ?> |
| 886 | - <div class="gallery-template-card <?php echo $selected_class; ?>" data-template-id="<?php echo esc_attr($template_id); ?>" data-is-premium="<?php echo isset($template['is_free']) && !$template['is_free'] ? 'true' : 'false'; ?>"> | |
| 896 | + <div class="gallery-template-card <?php echo esc_attr($selected_class); ?>" data-template-id="<?php echo esc_attr($template_id); ?>" data-is-premium="<?php echo isset($template['is_free']) && !$template['is_free'] ? 'true' : 'false'; ?>"> | |
| 887 | 897 | <div class="p-4 border rounded-lg"> |
| 888 | 898 | <div class="flex items-center mb-2"> |
| 889 | 899 | <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i> |
| 890 | 900 | <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span> |
| @@ -900,12 +910,12 @@ | ||
| 900 | 910 | </div> |
| 901 | 911 | |
| 902 | 912 | <div class="flex justify-end mt-4 space-x-3"> |
| 903 | 913 | <button type="button" id="apply-selected-template" class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> |
| 904 | - <?php _e('Apply', 'easy-invoice'); ?> | |
| 914 | + <?php esc_html_e('Apply', 'easy-invoice'); ?> | |
| 905 | 915 | </button> |
| 906 | 916 | <button type="button" class="gallery-close-btn inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> |
| 907 | - <?php _e('Close', 'easy-invoice'); ?> | |
| 917 | + <?php esc_html_e('Close', 'easy-invoice'); ?> | |
| 908 | 918 | </button> |
| 909 | 919 | </div> |
| 910 | 920 | </div> |
| 911 | 921 | </div> |
| @@ -917,9 +927,9 @@ | ||
| 917 | 927 | <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors"> |
| 918 | 928 | <i class="fas fa-chevron-down text-sm"></i> |
| 919 | 929 | </button> |
| 920 | 930 | <h3 class="text-base font-normal text-gray-800 flex-1"> |
| 921 | - <?php _e('New Item', 'easy-invoice'); ?> | |
| 931 | + <?php esc_html_e('New Item', 'easy-invoice'); ?> | |
| 922 | 932 | </h3> |
| 923 | 933 | </div> |
| 924 | 934 | <div class="flex items-center gap-3"> |
| 925 | 935 | <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full"> |
| @@ -925,9 +935,9 @@ | ||
| 925 | 935 | <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full"> |
| 926 | 936 | <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span> |
| 927 | 937 | = $<span class="total-summary">0.00</span> |
| 928 | 938 | </div> |
| 929 | - <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Fill this item with sample data', 'easy-invoice'); ?>"> | |
| 939 | + <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Fill this item with sample data', 'easy-invoice')); ?>"> | |
| 930 | 940 | <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block"> |
| 931 | 941 | <path d="M2 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H2zm0 1h16v14H2V3z"/> |
| 932 | 942 | <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/> |
| 933 | 943 | <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/> |
| @@ -934,14 +944,14 @@ | ||
| 934 | 944 | <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/> |
| 935 | 945 | </svg> |
| 936 | 946 | </button> |
| 937 | 947 | <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?> |
| 938 | - <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php _e('Select from Item Library', 'easy-invoice'); ?>"> | |
| 948 | + <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Select from Item Library', 'easy-invoice')); ?>"> | |
| 939 | 949 | <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block"> |
| 940 | 950 | <path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/> |
| 941 | 951 | </svg> |
| 942 | 952 | </button> |
| 943 | - <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php _e('Save to Item Library', 'easy-invoice'); ?>" data-item-index="" data-nonce="<?php echo wp_create_nonce('easy_invoice_pro_item_library'); ?>"> | |
| 953 | + <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Save to Item Library', 'easy-invoice')); ?>" data-item-index="" data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_pro_item_library')); ?>"> | |
| 944 | 954 | <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block"> |
| 945 | 955 | <path d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"/> |
| 946 | 956 | </svg> |
| 947 | 957 | </button> |
| @@ -946,14 +956,14 @@ | ||
| 946 | 956 | </svg> |
| 947 | 957 | </button> |
| 948 | 958 | <?php endif; ?> |
| 949 | 959 | <button type="button" class="remove-item text-gray-400 hover:text-red-600 hover:bg-red-50 p-2 rounded-lg transition-all duration-200 text-sm font-medium"> |
| 950 | - <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?> | |
| 960 | + <i class="fas fa-trash-alt mr-1"></i> <?php esc_html_e('Remove', 'easy-invoice'); ?> | |
| 951 | 961 | </button> |
| 952 | 962 | </div> |
| 953 | 963 | </div> |
| 954 | 964 | <div class="item-content"> |
| 955 | - <?php echo $quote_form_manager->generateItemTemplateHtml(); ?> | |
| 965 | + <?php echo $quote_form_manager->generateItemTemplateHtml(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- form markup built by ItemFieldRenderer, which escapes every value. ?> | |
| 956 | 966 | </div> |
| 957 | 967 | </div> |
| 958 | 968 | </template> |
| 959 | 969 | |
| @@ -1011,11 +1021,11 @@ | ||
| 1011 | 1021 | |
| 1012 | 1022 | if (isFreeVersion) { |
| 1013 | 1023 | // Show premium upgrade modal for free users |
| 1014 | 1024 | if (typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { |
| 1015 | - EasyInvoiceConfirmation.showFeatureUpgrade('<?php _e('Premium Templates', 'easy-invoice'); ?>', '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your quotes a unique and professional look. Browse through our complete template library and find the perfect design for your business.', 'easy-invoice'); ?>'); | |
| 1025 | + EasyInvoiceConfirmation.showFeatureUpgrade('<?php echo esc_js(__('Premium Templates', 'easy-invoice')); ?>', '<?php echo esc_js(__('Access all premium templates including Modern, Professional, Classic, and more to give your quotes a unique and professional look. Browse through our complete template library and find the perfect design for your business.', 'easy-invoice')); ?>'); | |
| 1016 | 1026 | } else { |
| 1017 | - alert('<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>'); | |
| 1027 | + alert('<?php echo esc_js(__('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice')); ?>'); | |
| 1018 | 1028 | } |
| 1019 | 1029 | } else { |
| 1020 | 1030 | // Pro users can browse all templates |
| 1021 | 1031 | $('#template-gallery-modal').show(); |
| @@ -1042,11 +1052,11 @@ | ||
| 1042 | 1052 | |
| 1043 | 1053 | if (isPremium && isFreeVersion) { |
| 1044 | 1054 | // Show premium upgrade modal instead of selecting the template |
| 1045 | 1055 | if (typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { |
| 1046 | - EasyInvoiceConfirmation.showFeatureUpgrade('<?php _e('Premium Templates', 'easy-invoice'); ?>', '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your quotes a unique and professional look.', 'easy-invoice'); ?>'); | |
| 1056 | + EasyInvoiceConfirmation.showFeatureUpgrade('<?php echo esc_js(__('Premium Templates', 'easy-invoice')); ?>', '<?php echo esc_js(__('Access all premium templates including Modern, Professional, Classic, and more to give your quotes a unique and professional look.', 'easy-invoice')); ?>'); | |
| 1047 | 1057 | } else { |
| 1048 | - alert('<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>'); | |
| 1058 | + alert('<?php echo esc_js(__('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice')); ?>'); | |
| 1049 | 1059 | } |
| 1050 | 1060 | return; |
| 1051 | 1061 | } |
| 1052 | 1062 | |
| @@ -1200,9 +1210,9 @@ | ||
| 1200 | 1210 | }); |
| 1201 | 1211 | |
| 1202 | 1212 | if (allCollapsed) { |
| 1203 | 1213 | // If all items are collapsed, expand them |
| 1204 | - $button.html('<i class="fas fa-chevron-down mr-1"></i> <?php _e('Collapse All', 'easy-invoice'); ?>'); | |
| 1214 | + $button.html('<i class="fas fa-chevron-down mr-1"></i> <?php esc_html_e('Collapse All', 'easy-invoice'); ?>'); | |
| 1205 | 1215 | $('.quote-item').each(function() { |
| 1206 | 1216 | const $item = $(this); |
| 1207 | 1217 | const itemContent = $item.find('.item-content'); |
| 1208 | 1218 | const summaryElement = $item.find('.item-collapsed-summary'); |
| @@ -1220,9 +1230,9 @@ | ||
| 1220 | 1230 | } |
| 1221 | 1231 | }); |
| 1222 | 1232 | } else { |
| 1223 | 1233 | // If any items are expanded, collapse them all |
| 1224 | - $button.html('<i class="fas fa-chevron-right mr-1"></i> <?php _e('Expand All', 'easy-invoice'); ?>'); | |
| 1234 | + $button.html('<i class="fas fa-chevron-right mr-1"></i> <?php esc_html_e('Expand All', 'easy-invoice'); ?>'); | |
| 1225 | 1235 | $('.quote-item').each(function() { |
| 1226 | 1236 | const $item = $(this); |
| 1227 | 1237 | const itemContent = $item.find('.item-content'); |
| 1228 | 1238 | const summaryElement = $item.find('.item-collapsed-summary'); |
| @@ -1252,9 +1262,10 @@ | ||
| 1252 | 1262 | }); |
| 1253 | 1263 | |
| 1254 | 1264 | // Client search functionality |
| 1255 | 1265 | let searchTimeout; |
| 1256 | - let isSearching = false; | |
| 1266 | + let isSearching = false; // the in-flight jqXHR, or false | |
| 1267 | + let searchSeq = 0; | |
| 1257 | 1268 | $('#client-search-input').on('input', function() { |
| 1258 | 1269 | const query = $(this).val().trim(); |
| 1259 | 1270 | clearTimeout(searchTimeout); |
| 1260 | 1271 | |
| @@ -1486,9 +1497,10 @@ | ||
| 1486 | 1497 | // Update the collapsed summary of a quote item |
| 1487 | 1498 | function updateQuoteItemSummary(itemContainer) { |
| 1488 | 1499 | const quantity = parseFloat(itemContainer.find('input[name*="[quantity]"]').val()) || 0; |
| 1489 | 1500 | const price = parseFloat(itemContainer.find('input[name*="[price]"]').val()) || 0; |
| 1490 | - const total = quantity * price; | |
| 1501 | + // Half-up to cents, matching PHP round($x, 2). | |
| 1502 | + const total = Number(Math.round(Number((quantity * price) + 'e2')) + 'e-2'); | |
| 1491 | 1503 | itemContainer.find('.quantity-summary').text(quantity); |
| 1492 | 1504 | itemContainer.find('.price-summary').text(price.toFixed(2)); |
| 1493 | 1505 | itemContainer.find('.total-summary').text(total.toFixed(2)); |
| 1494 | 1506 | itemContainer.find('input[name*="[total]"]').val(total.toFixed(2)); |
| @@ -1494,22 +1506,20 @@ | ||
| 1494 | 1506 | itemContainer.find('input[name*="[total]"]').val(total.toFixed(2)); |
| 1495 | 1507 | } |
| 1496 | 1508 | |
| 1497 | 1509 | function searchClients(query) { |
| 1498 | - | |
| 1499 | - | |
| 1500 | - // Prevent multiple simultaneous requests | |
| 1501 | - if (isSearching) { | |
| 1502 | - return; | |
| 1503 | - } | |
| 1504 | - | |
| 1505 | - // Clear any existing timeout to prevent multiple requests | |
| 1510 | + // Latest request wins. Clicking the box fires a "show all" search and the | |
| 1511 | + // user's first keystrokes arrive while it is in flight; refusing them here | |
| 1512 | + // (the old isSearching gate) left the picker showing the first 50 clients | |
| 1513 | + // instead of what was typed. Abort the in-flight one and ignore stale replies. | |
| 1506 | 1514 | if (searchTimeout) { |
| 1507 | 1515 | clearTimeout(searchTimeout); |
| 1508 | 1516 | } |
| 1517 | + if (isSearching && isSearching.abort) { | |
| 1518 | + isSearching.abort(); | |
| 1519 | + } | |
| 1520 | + const requestSeq = ++searchSeq; | |
| 1509 | 1521 | |
| 1510 | - isSearching = true; | |
| 1511 | - | |
| 1512 | 1522 | $('#client-search-loading').show().removeClass('hidden'); |
| 1513 | 1523 | $('#client-search-empty').hide().addClass('hidden'); |
| 1514 | 1524 | $('#client-search-list').empty(); |
| 1515 | 1525 | |
| @@ -1519,9 +1529,9 @@ | ||
| 1519 | 1529 | } |
| 1520 | 1530 | |
| 1521 | 1531 | |
| 1522 | 1532 | |
| 1523 | - $.ajax({ | |
| 1533 | + isSearching = $.ajax({ | |
| 1524 | 1534 | url: easyInvoice.ajaxUrl, |
| 1525 | 1535 | type: 'POST', |
| 1526 | 1536 | data: { |
| 1527 | 1537 | action: 'easy_invoice_search_clients', |
| @@ -1529,9 +1539,11 @@ | ||
| 1529 | 1539 | nonce: easyInvoice.nonce |
| 1530 | 1540 | }, |
| 1531 | 1541 | |
| 1532 | 1542 | success: function(response) { |
| 1533 | - | |
| 1543 | + if (requestSeq !== searchSeq) { | |
| 1544 | + return; // a newer search has replaced this one | |
| 1545 | + } | |
| 1534 | 1546 | isSearching = false; |
| 1535 | 1547 | $('#client-search-loading').hide().addClass('hidden'); |
| 1536 | 1548 | |
| 1537 | 1549 | if (response.success && response.data.length > 0) { |
| @@ -1536,21 +1548,22 @@ | ||
| 1536 | 1548 | |
| 1537 | 1549 | if (response.success && response.data.length > 0) { |
| 1538 | 1550 | |
| 1539 | 1551 | response.data.forEach(function(client) { |
| 1540 | - const option = $(` | |
| 1541 | - <div class="client-option px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0" | |
| 1542 | - data-client-id="${client.id}" | |
| 1543 | - data-client-name="${(client.name || '').replace(/"/g, '"')}" | |
| 1544 | - data-client-email="${(client.email || '').replace(/"/g, '"')}" | |
| 1545 | - data-client-company="${(client.company || '').replace(/"/g, '"')}" | |
| 1546 | - data-client-phone="${(client.phone || '').replace(/"/g, '"')}" | |
| 1547 | - data-client-address="${(client.address || '').replace(/"/g, '"')}" | |
| 1548 | - onclick="selectClientFromOption(this)"> | |
| 1549 | - <div class="font-medium text-gray-900">${client.name || 'No Name'}</div> | |
| 1550 | - <div class="text-sm text-gray-600">${client.email || 'No Email'}</div> | |
| 1551 | - </div> | |
| 1552 | - `); | |
| 1552 | + // Built with jQuery, never by string interpolation: the name, company | |
| 1553 | + // and address are what the client typed into their portal profile. | |
| 1554 | + const option = $('<div>', { | |
| 1555 | + 'class': 'client-option px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0', | |
| 1556 | + 'data-client-id': client.id, | |
| 1557 | + 'data-client-name': client.name || '', | |
| 1558 | + 'data-client-email': client.email || '', | |
| 1559 | + 'data-client-company': client.company || '', | |
| 1560 | + 'data-client-phone': client.phone || '', | |
| 1561 | + 'data-client-website': client.website || '', | |
| 1562 | + 'data-client-address': client.address || '' | |
| 1563 | + }).on('click', function() { selectClientFromOption(this); }) | |
| 1564 | + .append($('<div>', { 'class': 'font-medium text-gray-900' }).text(client.name || '')) | |
| 1565 | + .append($('<div>', { 'class': 'text-sm text-gray-600' }).text(client.email || '')); | |
| 1553 | 1566 | $('#client-search-list').append(option); |
| 1554 | 1567 | }); |
| 1555 | 1568 | $('#client-search-results').show().removeClass('hidden'); |
| 1556 | 1569 | |
| @@ -1559,8 +1572,11 @@ | ||
| 1559 | 1572 | $('#client-search-results').show().removeClass('hidden'); |
| 1560 | 1573 | } |
| 1561 | 1574 | }, |
| 1562 | 1575 | error: function(xhr, status, error) { |
| 1576 | + if (status === 'abort' || requestSeq !== searchSeq) { | |
| 1577 | + return; // superseded by a newer search | |
| 1578 | + } | |
| 1563 | 1579 | console.error('Quote form: AJAX error:', error); |
| 1564 | 1580 | console.error('Quote form: Status:', status); |
| 1565 | 1581 | console.error('Quote form: Response:', xhr.responseText); |
| 1566 | 1582 | isSearching = false; |
| @@ -1581,9 +1597,10 @@ | ||
| 1581 | 1597 | $('#client-info-display').show().removeClass('hidden'); |
| 1582 | 1598 | $('#no-client-message').hide().addClass('hidden'); |
| 1583 | 1599 | |
| 1584 | 1600 | // Update the edit client button URL |
| 1585 | - $('#edit-selected-client').attr('href', '<?php echo admin_url('admin.php?page=easy-invoice-client-edit&client_id='); ?>' + clientId); | |
| 1601 | + $('#edit-selected-client').attr('href', '<?php echo esc_url(admin_url('admin.php?page=easy-invoice-client-edit&client_id=')); ?>' + clientId); | |
| 1602 | + $('#edit-selected-client').toggleClass('hidden', !clientId); | |
| 1586 | 1603 | |
| 1587 | 1604 | // If we have full client data, populate all fields directly |
| 1588 | 1605 | if (clientData) { |
| 1589 | 1606 | $('#display-client-name').text(clientData.name || clientName || '-'); |
| @@ -1786,10 +1803,10 @@ | ||
| 1786 | 1803 | Cancel |
| 1787 | 1804 | </button> |
| 1788 | 1805 | <button type="submit" id="save-quote-btn" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> |
| 1789 | 1806 | <i class="fas fa-save mr-2"></i> |
| 1790 | - <span>Save Quote</span> | |
| 1807 | + <span><?php esc_html_e('Save Quote', 'easy-invoice'); ?></span> | |
| 1791 | 1808 | </button> |
| 1792 | 1809 | </div> |
| 1793 | 1810 | </div> |
| 1794 | 1811 | </form> |
| 1795 | 1812 | <?php endif; ?> |