PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
← All changes | templates/invoices/form.php +129 -121 2.3.6 → 2.4.0 View file →
@@ -1,5 +1,8 @@
1 1 <?php
2 +if ( ! defined( 'ABSPATH' ) ) {
3 + exit;
4 +}
2 5 // Enqueue CSS and JS files
3 6 wp_enqueue_style('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/css/invoice-form.css', array(), '1.0.0');
4 7 wp_enqueue_script('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/js/invoice-form.js', array('jquery'), '1.0.0', true);
5 8
@@ -222,9 +225,9 @@
222 225 <?php endif; ?>
223 226 <form id="invoice-form"<?php echo $ei_can_edit ? '' : ' data-ei-locked="1"'; ?>>
224 227 <input type="hidden" id="invoice-id" name="invoice_id" value="<?php echo $invoice_id ? esc_attr($invoice_id) : ''; ?>">
225 228
226 - <input type="hidden" id="invoice_nonce" name="invoice_nonce" value="<?php echo wp_create_nonce('easy_invoice_nonce'); ?>">
229 + <input type="hidden" id="invoice_nonce" name="invoice_nonce" value="<?php echo esc_attr(wp_create_nonce('easy_invoice_nonce')); ?>">
227 230
228 231 <?php foreach ($tabs as $tab): ?>
229 232 <div class="tab-content <?php echo $tab['active'] ? 'active' : 'hidden'; ?> px-6 py-5" id="<?php echo esc_attr($tab['id']); ?>">
230 233 <?php if ($tab['id'] === 'invoice-tab'): ?>
@@ -229,11 +232,11 @@
229 232 <div class="tab-content <?php echo $tab['active'] ? 'active' : 'hidden'; ?> px-6 py-5" id="<?php echo esc_attr($tab['id']); ?>">
230 233 <?php if ($tab['id'] === 'invoice-tab'): ?>
231 234 <div class="mb-8 pb-6 border-b border-gray-200">
232 235 <div class="flex justify-between items-center mb-4">
233 - <h3 class="text-lg font-medium text-gray-900"><?php _e('Invoice Template', 'easy-invoice'); ?></h3>
236 + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Invoice Template', 'easy-invoice'); ?></h3>
234 237 <button type="button" id="browse-all-templates" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
235 - <span class="mr-1"><?php _e('Browse Invoice Templates', 'easy-invoice'); ?></span>
238 + <span class="mr-1"><?php esc_html_e('Browse Invoice Templates', 'easy-invoice'); ?></span>
236 239 <i class="fas fa-chevron-right"></i>
237 240 </button>
238 241 </div>
239 242
@@ -303,10 +306,12 @@
303 306 if ($is_free_version && $is_premium) {
304 307 $premium_classes = 'premium-template-blurred';
305 308 }
306 309 ?>
307 - <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'; ?>">
308 - <input type="radio" id="template-<?php echo esc_attr($template_id); ?>" name="invoice_template" value="<?php echo esc_attr($template_id); ?>" class="template-radio" <?php echo $checked; ?> <?php echo ($is_free_version && $is_premium) ? 'disabled' : ''; ?>>
310 + <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'); ?>">
311 + <?php // aria-label: the visible template name sits in sibling markup, not in a
312 + // <label for>, so assistive tech announced these radios as unnamed. ?>
313 + <input type="radio" id="template-<?php /* translators: %s: template name. */ echo esc_attr($template_id); ?>" name="invoice_template" value="<?php echo esc_attr($template_id); ?>" aria-label="<?php echo esc_attr(sprintf(__('Use the %s invoice template', 'easy-invoice'), $template['name'])); ?>" class="template-radio" <?php echo esc_html($checked); ?> <?php echo esc_attr(($is_free_version && $is_premium) ? 'disabled' : ''); ?>>
309 314 <div class="p-4 border rounded-lg">
310 315 <div class="flex items-center mb-2">
311 316 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
312 317 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
@@ -328,9 +333,9 @@
328 333 foreach ($main_fields as $field):
329 334 // Skip fields that have a section (like notes) - they're rendered separately
330 335 // Also skip the template field as it's rendered manually in the template selection section
331 336 if (!isset($field['section']) && $field['name'] !== 'invoice_template') {
332 - echo $form_manager->renderField($field, $invoice);
337 + echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
333 338 }
334 339 endforeach;
335 340 ?>
336 341 </div>
@@ -342,9 +347,9 @@
342 347 <i class="fas fa-info-circle text-blue-400"></i>
343 348 </div>
344 349 <div class="ml-3">
345 350 <p class="text-sm text-blue-700">
346 - <?php _e('Invoice number will be automatically generated when you save the invoice.', 'easy-invoice'); ?>
351 + <?php esc_html_e('Invoice number will be automatically generated when you save the invoice.', 'easy-invoice'); ?>
347 352 </p>
348 353 </div>
349 354 </div>
350 355 </div>
@@ -355,13 +360,13 @@
355 360 $recurring_fields = $form_manager->getFieldsBySection('invoice-tab', 'recurring');
356 361 if (!empty($recurring_fields)):
357 362 ?>
358 363 <div class="mt-8 border-t border-gray-200 pt-6">
359 - <h3 class="text-lg font-medium text-gray-900"><?php _e('Recurring Invoice Settings', 'easy-invoice-pro'); ?></h3>
364 + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Recurring Invoice Settings', 'easy-invoice'); ?></h3>
360 365 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
361 366 <?php
362 367 foreach ($recurring_fields as $field):
363 - echo $form_manager->renderField($field, $invoice);
368 + echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
364 369 endforeach;
365 370 ?>
366 371 </div>
367 372 </div>
@@ -375,27 +380,27 @@
375 380 <i class="fas fa-sync-alt text-white text-sm"></i>
376 381 </div>
377 382 </div>
378 383 <div class="ml-3 flex-1">
379 - <h3 class="text-base font-semibold text-gray-900 mb-1"><?php _e('Recurring Invoices', 'easy-invoice'); ?></h3>
380 - <p class="text-sm text-gray-600 mb-3"><?php _e('Automate your billing with recurring invoices. Set up weekly, monthly, or custom intervals to automatically generate and send invoices to your clients.', 'easy-invoice'); ?></p>
384 + <h3 class="text-base font-semibold text-gray-900 mb-1"><?php esc_html_e('Recurring Invoices', 'easy-invoice'); ?></h3>
385 + <p class="text-sm text-gray-600 mb-3"><?php esc_html_e('Automate your billing with recurring invoices. Set up weekly, monthly, or custom intervals to automatically generate and send invoices to your clients.', 'easy-invoice'); ?></p>
381 386 <div class="flex items-center space-x-3 mb-3">
382 387 <div class="flex items-center text-xs text-gray-500">
383 388 <i class="fas fa-check text-green-500 mr-1"></i>
384 - <span><?php _e('Automated billing cycles', 'easy-invoice'); ?></span>
389 + <span><?php esc_html_e('Automated billing cycles', 'easy-invoice'); ?></span>
385 390 </div>
386 391 <div class="flex items-center text-xs text-gray-500">
387 392 <i class="fas fa-check text-green-500 mr-1"></i>
388 - <span><?php _e('Custom intervals', 'easy-invoice'); ?></span>
393 + <span><?php esc_html_e('Custom intervals', 'easy-invoice'); ?></span>
389 394 </div>
390 395 <div class="flex items-center text-xs text-gray-500">
391 396 <i class="fas fa-check text-green-500 mr-1"></i>
392 - <span><?php _e('Auto-send notifications', 'easy-invoice'); ?></span>
397 + <span><?php esc_html_e('Auto-send notifications', 'easy-invoice'); ?></span>
393 398 </div>
394 399 </div>
395 - <button type="button" class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200" onclick="if(typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { EasyInvoiceConfirmation.showFeatureUpgrade('<?php _e('Recurring Invoices', 'easy-invoice'); ?>', '<?php _e('Automate your billing with recurring invoices. Set up weekly, monthly, or custom intervals to automatically generate and send invoices to your clients.', 'easy-invoice'); ?>'); } else { alert('<?php _e('This is a premium feature. Please upgrade to Pro to access recurring invoices.', 'easy-invoice'); ?>'); }">
400 + <button type="button" class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200" onclick="if(typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { EasyInvoiceConfirmation.showFeatureUpgrade('<?php echo esc_js(__('Recurring Invoices', 'easy-invoice')); ?>', '<?php echo esc_js(__('Automate your billing with recurring invoices. Set up weekly, monthly, or custom intervals to automatically generate and send invoices to your clients.', 'easy-invoice')); ?>'); } else { alert('<?php echo esc_js(__('This is a premium feature. Please upgrade to Pro to access recurring invoices.', 'easy-invoice')); ?>'); }">
396 401 <i class="fas fa-crown mr-1.5"></i>
397 - <?php _e('Upgrade to Pro', 'easy-invoice'); ?>
402 + <?php esc_html_e('Upgrade to Pro', 'easy-invoice'); ?>
398 403 </button>
399 404 </div>
400 405 </div>
401 406 </div>
@@ -410,13 +415,13 @@
410 415 $subscription_fields = $form_manager->getFieldsBySection('invoice-tab', 'subscription');
411 416 if (!empty($subscription_fields)):
412 417 ?>
413 418 <div class="mt-8 border-t border-gray-200 pt-6">
414 - <h3 class="text-lg font-medium text-gray-900"><?php _e('Subscription Settings', 'easy-invoice-pro'); ?></h3>
419 + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Subscription Settings', 'easy-invoice'); ?></h3>
415 420 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
416 421 <?php
417 422 foreach ($subscription_fields as $field):
418 - echo $form_manager->renderField($field, $invoice);
423 + echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
419 424 endforeach;
420 425 ?>
421 426 </div>
422 427 </div>
@@ -431,27 +436,27 @@
431 436 <i class="fas fa-calendar-alt text-white text-sm"></i>
432 437 </div>
433 438 </div>
434 439 <div class="ml-3 flex-1">
435 - <h3 class="text-base font-semibold text-gray-900 mb-1"><?php _e('Partial Payment', 'easy-invoice'); ?></h3>
436 - <p class="text-sm text-gray-600 mb-3"><?php _e('Accept partial payments from clients with flexible payment schedules, payment tracking, and outstanding balance management.', 'easy-invoice'); ?></p>
440 + <h3 class="text-base font-semibold text-gray-900 mb-1"><?php esc_html_e('Partial Payment', 'easy-invoice'); ?></h3>
441 + <p class="text-sm text-gray-600 mb-3"><?php esc_html_e('Accept partial payments from clients with flexible payment schedules, payment tracking, and outstanding balance management.', 'easy-invoice'); ?></p>
437 442 <div class="flex items-center space-x-3 mb-3">
438 443 <div class="flex items-center text-xs text-gray-500">
439 444 <i class="fas fa-check text-green-500 mr-1"></i>
440 - <span><?php _e('Flexible payment schedules', 'easy-invoice'); ?></span>
445 + <span><?php esc_html_e('Flexible payment schedules', 'easy-invoice'); ?></span>
441 446 </div>
442 447 <div class="flex items-center text-xs text-gray-500">
443 448 <i class="fas fa-check text-green-500 mr-1"></i>
444 - <span><?php _e('Payment tracking', 'easy-invoice'); ?></span>
449 + <span><?php esc_html_e('Payment tracking', 'easy-invoice'); ?></span>
445 450 </div>
446 451 <div class="flex items-center text-xs text-gray-500">
447 452 <i class="fas fa-check text-green-500 mr-1"></i>
448 - <span><?php _e('Balance management', 'easy-invoice'); ?></span>
453 + <span><?php esc_html_e('Balance management', 'easy-invoice'); ?></span>
449 454 </div>
450 455 </div>
451 - <button type="button" class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200" onclick="if(typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { EasyInvoiceConfirmation.showFeatureUpgrade('<?php _e('Partial Payment', 'easy-invoice'); ?>', '<?php _e('Accept partial payments from clients with flexible payment schedules, payment tracking, and outstanding balance management.', 'easy-invoice'); ?>'); } else { alert('<?php _e('This is a premium feature. Please upgrade to Pro to access partial payment functionality.', 'easy-invoice'); ?>'); }">
456 + <button type="button" class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200" onclick="if(typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { EasyInvoiceConfirmation.showFeatureUpgrade('<?php echo esc_js(__('Partial Payment', 'easy-invoice')); ?>', '<?php echo esc_js(__('Accept partial payments from clients with flexible payment schedules, payment tracking, and outstanding balance management.', 'easy-invoice')); ?>'); } else { alert('<?php echo esc_js(__('This is a premium feature. Please upgrade to Pro to access partial payment functionality.', 'easy-invoice')); ?>'); }">
452 457 <i class="fas fa-crown mr-1.5"></i>
453 - <?php _e('Upgrade to Pro', 'easy-invoice'); ?>
458 + <?php esc_html_e('Upgrade to Pro', 'easy-invoice'); ?>
454 459 </button>
455 460 </div>
456 461 </div>
457 462 </div>
@@ -458,15 +463,15 @@
458 463 </div>
459 464 <?php endif; ?>
460 465
461 466 <div class="mt-8 border-t border-gray-200 pt-6">
462 - <h3 class="text-lg font-medium text-gray-900"><?php _e('Notes & Terms', 'easy-invoice'); ?></h3>
467 + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Notes & Terms', 'easy-invoice'); ?></h3>
463 468 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
464 469 <?php
465 470 // Only render notes and terms fields, not all invoice tab fields
466 471 $notes_fields = $form_manager->getFieldsBySection('invoice-tab', 'notes');
467 472 foreach ($notes_fields as $field):
468 - echo $form_manager->renderField($field, $invoice);
473 + echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
469 474 endforeach;
470 475 ?>
471 476 </div>
472 477 </div>
@@ -472,12 +477,12 @@
472 477 </div>
473 478
474 479 <?php elseif ($tab['id'] === 'items-tab'): ?>
475 480 <div class="flex justify-between items-center mb-6">
476 - <h2 class="text-lg font-medium text-gray-900"><?php _e('Invoice Items', 'easy-invoice'); ?></h2>
481 + <h2 class="text-lg font-medium text-gray-900"><?php esc_html_e('Invoice Items', 'easy-invoice'); ?></h2>
477 482 <button type="button" id="collapse-all-items" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
478 483 <i class="fas fa-chevron-down mr-1"></i>
479 - <span><?php _e('Collapse All', 'easy-invoice'); ?></span>
484 + <span><?php esc_html_e('Collapse All', 'easy-invoice'); ?></span>
480 485 </button>
481 486 </div>
482 487
483 488 <div class="space-y-4 invoice-items-container">
@@ -486,9 +491,9 @@
486 491 if ($invoice && $invoice->getItems()) :
487 492 $index = 0;
488 493 foreach ($invoice->getItems() as $item) :
489 494 // Generate a unique ID for the item
490 - $item_id = 'item_' . time() . '_' . $index . '_' . rand(1000, 9999);
495 + $item_id = 'item_' . time() . '_' . $index . '_' . wp_rand(1000, 9999);
491 496
492 497 // Get display title for header using dynamic field access
493 498 $title = $item->getName();
494 499
@@ -499,12 +504,13 @@
499 504 $title = $item_data['title'];
500 505 }
501 506 }
502 507
508 + /* translators: %d: title. */
503 509 $display_title = !empty($title) ? $title : sprintf(__('Item #%d', 'easy-invoice'), $index + 1);
504 510 $short_title = strlen($display_title) > 30 ? substr($display_title, 0, 30) . '...' : $display_title;
505 511 ?>
506 - <div class="invoice-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; ?>">
512 + <div class="invoice-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; ?>">
507 513 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
508 514 <div class="flex items-center">
509 515 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
510 516 <i class="fas fa-chevron-down text-sm"></i>
@@ -510,9 +516,9 @@
510 516 <i class="fas fa-chevron-down text-sm"></i>
511 517 </button>
512 518 <h3 class="text-base font-normal text-gray-800 flex-1">
513 519 <?php
514 - echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
520 + echo !empty($title) ? esc_html($title) : esc_html__('New Item', 'easy-invoice');
515 521 ?>
516 522 </h3>
517 523 </div>
518 524 <div class="flex items-center gap-3">
@@ -519,9 +525,9 @@
519 525 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
520 526 <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>
521 527 = $<span class="total-summary font-medium"><?php echo esc_html(number_format($item->getAmount(), 2)); ?></span>
522 528 </div>
523 - <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'); ?>">
529 + <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')); ?>">
524 530 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
525 531 <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"/>
526 532 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
527 533 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
@@ -528,14 +534,14 @@
528 534 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
529 535 </svg>
530 536 </button>
531 537 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
532 - <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'); ?>">
538 + <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')); ?>">
533 539 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
534 540 <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"/>
535 541 </svg>
536 542 </button>
537 - <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'); ?>">
543 + <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')); ?>">
538 544 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
539 545 <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"/>
540 546 </svg>
541 547 </button>
@@ -540,14 +546,15 @@
540 546 </svg>
541 547 </button>
542 548 <?php endif; ?>
543 549 <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">
544 - <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
550 + <i class="fas fa-trash-alt mr-1"></i> <?php esc_html_e('Remove', 'easy-invoice'); ?>
545 551 </button>
546 552 </div>
547 553 </div>
548 554 <div class="item-content">
549 - <?php echo $form_manager->generateExistingItemHtml($item, $index); ?>
555 + <?php echo $form_manager->generateExistingItemHtml($item, $index); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- ItemFieldRenderer escapes every value it emits.
556 +?>
550 557 </div>
551 558 </div>
552 559 <?php
553 560 $index++;
@@ -583,9 +590,9 @@
583 590 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
584 591 <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span>
585 592 = $<span class="total-summary">0.00</span>
586 593 </div>
587 - <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'); ?>">
594 + <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')); ?>">
588 595 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
589 596 <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"/>
590 597 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
591 598 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
@@ -592,14 +599,14 @@
592 599 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
593 600 </svg>
594 601 </button>
595 602 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
596 - <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'); ?>">
603 + <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')); ?>">
597 604 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
598 605 <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"/>
599 606 </svg>
600 607 </button>
601 - <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'); ?>">
608 + <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')); ?>">
602 609 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
603 610 <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"/>
604 611 </svg>
605 612 </button>
@@ -604,14 +611,14 @@
604 611 </svg>
605 612 </button>
606 613 <?php endif; ?>
607 614 <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">
608 - <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
615 + <i class="fas fa-trash-alt mr-1"></i> <?php esc_html_e('Remove', 'easy-invoice'); ?>
609 616 </button>
610 617 </div>
611 618 </div>
612 619 <div class="item-content">
613 - <?php echo $form_manager->generateItemTemplateHtml(); ?>
620 + <?php echo $form_manager->generateItemTemplateHtml(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- form markup built by ItemFieldRenderer, which escapes every value. ?>
614 621 </div>
615 622 </div>
616 623 <?php endif; ?>
617 624 </div>
@@ -622,10 +629,10 @@
622 629 <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">
623 630 <i class="fas fa-plus text-indigo-600 text-lg"></i>
624 631 </div>
625 632 <div class="text-left">
626 - <span class="font-semibold text-base"><?php _e('Add Another Item', 'easy-invoice'); ?></span>
627 - <p class="text-sm text-indigo-600 mt-0.5"><?php _e('Click to add a new line item', 'easy-invoice'); ?></p>
633 + <span class="font-semibold text-base"><?php esc_html_e('Add Another Item', 'easy-invoice'); ?></span>
634 + <p class="text-sm text-indigo-600 mt-0.5"><?php esc_html_e('Click to add a new line item', 'easy-invoice'); ?></p>
628 635 </div>
629 636 </div>
630 637 </button>
631 638 </div>
@@ -630,9 +637,9 @@
630 637 </button>
631 638 </div>
632 639 <?php elseif ($tab['id'] === 'client-tab'): ?>
633 640 <div class="mb-6">
634 - <label class="block text-sm font-medium text-gray-700 mb-3"><?php _e('Select Existing Client', 'easy-invoice'); ?></label>
641 + <label class="block text-sm font-medium text-gray-700 mb-3"><?php esc_html_e('Select Existing Client', 'easy-invoice'); ?></label>
635 642
636 643 <div class="relative">
637 644 <div class="flex items-center space-x-3">
638 645 <div class="relative flex-grow">
@@ -642,15 +649,15 @@
642 649 <input
643 650 type="text"
644 651 id="client-search-input"
645 652 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"
646 - placeholder="<?php _e('Search clients by name, email, or company...', 'easy-invoice'); ?>"
653 + placeholder="<?php echo esc_js(__('Search clients by name, email, or company...', 'easy-invoice')); ?>"
647 654 autocomplete="off"
648 655 >
649 656 </div>
650 657 <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">
651 658 <i class="fas fa-plus -ml-1 mr-2"></i>
652 - <?php _e('New Client', 'easy-invoice'); ?>
659 + <?php esc_html_e('New Client', 'easy-invoice'); ?>
653 660 </button>
654 661 </div>
655 662
656 663 <div id="client-search-results" class="absolute z-50 w-full mt-2 bg-white border border-gray-200 rounded-lg shadow-xl max-h-80 overflow-y-auto" style="display: none;">
@@ -655,13 +662,13 @@
655 662
656 663 <div id="client-search-results" class="absolute z-50 w-full mt-2 bg-white border border-gray-200 rounded-lg shadow-xl max-h-80 overflow-y-auto" style="display: none;">
657 664 <div id="client-search-loading" class="p-6 text-center text-gray-500" style="display: none;">
658 665 <i class="fas fa-spinner fa-spin mr-2"></i>
659 - <?php _e('Searching...', 'easy-invoice'); ?>
666 + <?php esc_html_e('Searching...', 'easy-invoice'); ?>
660 667 </div>
661 668 <div id="client-search-empty" class="p-6 text-center text-gray-500" style="display: none;">
662 669 <i class="fas fa-search mr-2"></i>
663 - <?php _e('No clients found', 'easy-invoice'); ?>
670 + <?php esc_html_e('No clients found', 'easy-invoice'); ?>
664 671 </div>
665 672 <div id="client-search-list" class="py-2">
666 673 </div>
667 674 </div>
@@ -712,14 +719,18 @@
712 719 </div>
713 720 </div>
714 721
715 722 <select id="select-client" class="hidden">
716 - <option value=""><?php _e('-- Select a client --', 'easy-invoice'); ?></option>
723 + <option value=""><?php esc_html_e('-- Select a client --', 'easy-invoice'); ?></option>
717 724 <?php
718 725 // Ensure $clients is defined
719 - if (!isset($clients)) {
720 - $client_repository = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository();
721 - $clients = $client_repository->all();
726 + if (!isset($clients) || empty($clients)) {
727 + $clients = [];
728 + $ei_selected_client_id = ($invoice && method_exists($invoice, 'getClientId')) ? (int) $invoice->getClientId() : 0;
729 + if ($ei_selected_client_id > 0) {
730 + $ei_selected_client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($ei_selected_client_id);
731 + if ($ei_selected_client) { $clients = [ $ei_selected_client ]; }
732 + }
722 733 }
723 734
724 735 // Output client options
725 736 if (!empty($clients)) {
@@ -745,9 +756,9 @@
745 756 $selected = $invoice && $invoice->getClientId() == $client->getId() ? 'selected' : '';
746 757 printf(
747 758 '<option value="%s" %s data-name="%s" data-email="%s">%s (%s)</option>',
748 759 esc_attr($client->getId()),
749 - $selected,
760 + $selected, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output of selected( ..., false ).
750 761 esc_attr($client_name),
751 762 esc_attr($client_email),
752 763 esc_html($client_name),
753 764 esc_html($client_email)
@@ -765,16 +776,16 @@
765 776 <div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
766 777 <div class="flex items-center justify-between pb-4 border-b border-gray-200 mb-4">
767 778 <h4 class="text-lg font-semibold text-gray-800 flex items-center">
768 779 <i class="fas fa-user-check text-indigo-500 mr-3"></i>
769 - <span><?php _e('Client Information', 'easy-invoice'); ?></span>
780 + <span><?php esc_html_e('Client Information', 'easy-invoice'); ?></span>
770 781 </h4>
771 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-client-edit&client_id=' . ($invoice && $invoice->getClientId() ? $invoice->getClientId() : '')); ?>"
782 + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-client-edit&client_id=' . ($invoice && $invoice->getClientId() ? $invoice->getClientId() : ''))); ?>"
772 783 id="edit-selected-client"
773 - class="text-sm text-indigo-600 hover:text-indigo-800 font-medium"
784 + class="text-sm text-indigo-600 hover:text-indigo-800 font-medium<?php echo ($invoice && $invoice->getClientId()) ? '' : ' hidden'; ?>"
774 785 target="_blank">
775 786 <i class="fas fa-pen mr-1"></i>
776 - <?php _e('Edit', 'easy-invoice'); ?>
787 + <?php esc_html_e('Edit', 'easy-invoice'); ?>
777 788 </a>
778 789 </div>
779 790
780 791 <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2">
@@ -780,9 +791,9 @@
780 791 <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2">
781 792 <div class="flex items-start group">
782 793 <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>
783 794 <div class="ml-3">
784 - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Name', 'easy-invoice'); ?></label>
795 + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Name', 'easy-invoice'); ?></label>
785 796 <p id="display-client-name" class="text-sm font-semibold text-gray-800 mt-1">
786 797 <?php
787 798 if ($invoice && $invoice->getClientId()) {
788 799 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -801,9 +812,9 @@
801 812
802 813 <div class="flex items-start group">
803 814 <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>
804 815 <div class="ml-3">
805 - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Company', 'easy-invoice'); ?></label>
816 + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Company', 'easy-invoice'); ?></label>
806 817 <p id="display-client-company" class="text-sm font-semibold text-gray-800 mt-1">
807 818 <?php
808 819 if ($invoice && $invoice->getClientId()) {
809 820 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -822,9 +833,9 @@
822 833
823 834 <div class="flex items-start group">
824 835 <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>
825 836 <div class="ml-3">
826 - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Email', 'easy-invoice'); ?></label>
837 + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Email', 'easy-invoice'); ?></label>
827 838 <p id="display-client-email" class="text-sm text-gray-800 mt-1 break-all">
828 839 <?php
829 840 if ($invoice && $invoice->getClientId()) {
830 841 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -843,9 +854,9 @@
843 854
844 855 <div class="flex items-start group">
845 856 <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>
846 857 <div class="ml-3">
847 - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Phone', 'easy-invoice'); ?></label>
858 + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Phone', 'easy-invoice'); ?></label>
848 859 <p id="display-client-phone" class="text-sm text-gray-800 mt-1">
849 860 <?php
850 861 if ($invoice && $invoice->getClientId()) {
851 862 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -864,9 +875,9 @@
864 875
865 876 <div class="flex items-start group md:col-span-2">
866 877 <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>
867 878 <div class="ml-3">
868 - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Website', 'easy-invoice'); ?></label>
879 + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Website', 'easy-invoice'); ?></label>
869 880 <p id="display-client-website" class="text-sm text-gray-800 mt-1">
870 881 <?php
871 882 if ($invoice && $invoice->getClientId()) {
872 883 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -885,9 +896,9 @@
885 896
886 897 <div class="flex items-start group md:col-span-2">
887 898 <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>
888 899 <div class="ml-3">
889 - <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php _e('Address', 'easy-invoice'); ?></label>
900 + <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Address', 'easy-invoice'); ?></label>
890 901 <p id="display-client-address" class="text-sm text-gray-800 mt-1 whitespace-pre-wrap">
891 902 <?php
892 903 if ($invoice && $invoice->getClientId()) {
893 904 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -907,14 +918,14 @@
907 918 </div>
908 919 </div>
909 920
910 921 <div id="no-client-message" class="mt-4 text-center py-6 border-2 border-dashed border-gray-300 rounded-lg <?php echo $invoice && $invoice->getClientId() ? 'hidden' : ''; ?>">
911 - <p class="text-gray-600"><?php _e('Select a client from the dropdown above to view their information.', 'easy-invoice'); ?></p>
922 + <p class="text-gray-600"><?php esc_html_e('Select a client from the dropdown above to view their information.', 'easy-invoice'); ?></p>
912 923 </div>
913 924 </div>
914 925 <?php elseif ($tab['id'] === 'discounts-taxes-tab'): ?>
915 926 <div>
916 - <h3 class="text-lg font-medium text-gray-900"><?php _e('Discount & Tax Settings', 'easy-invoice'); ?></h3>
927 + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Discount & Tax Settings', 'easy-invoice'); ?></h3>
917 928 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
918 929 <?php
919 930 // Get all discount and tax fields
920 931 $discount_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'discount');
@@ -927,9 +938,9 @@
927 938 return ($a['order'] ?? 0) - ($b['order'] ?? 0);
928 939 });
929 940
930 941 foreach ($all_fields as $field):
931 - echo $form_manager->renderField($field, $invoice);
942 + echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
932 943 endforeach;
933 944 ?>
934 945 </div>
935 946 </div>
@@ -934,25 +945,25 @@
934 945 </div>
935 946 </div>
936 947 <?php elseif ($tab['id'] === 'payments-tab'): ?>
937 948 <div>
938 - <h3 class="text-lg font-medium text-gray-900"><?php _e('Payment Information', 'easy-invoice'); ?></h3>
949 + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Payment Information', 'easy-invoice'); ?></h3>
939 950 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
940 951 <?php
941 952 $payment_fields = $form_manager->getFieldsBySection('payments-tab', 'payment');
942 953 foreach ($payment_fields as $field):
943 - echo $form_manager->renderField($field, $invoice);
954 + echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
944 955 endforeach;
945 956 ?>
946 957 </div>
947 958
948 959 <div class="mt-8 border-t border-gray-200 pt-6">
949 - <h3 class="text-lg font-medium text-gray-900"><?php _e('Currency', 'easy-invoice'); ?></h3>
960 + <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Currency', 'easy-invoice'); ?></h3>
950 961 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
951 962 <?php
952 963 $currency_fields = $form_manager->getFieldsBySection('payments-tab', 'currency');
953 964 foreach ($currency_fields as $field):
954 - echo $form_manager->renderField($field, $invoice);
965 + echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
955 966 endforeach;
956 967 ?>
957 968 </div>
958 969 </div>
@@ -965,11 +976,11 @@
965 976
966 977 <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">
967 978 <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">
968 979 <div class="flex justify-between items-center mb-4">
969 - <h3 class="text-xl font-medium text-gray-900"><?php _e('Browse Invoice Templates', 'easy-invoice'); ?></h3>
980 + <h3 class="text-xl font-medium text-gray-900"><?php esc_html_e('Browse Invoice Templates', 'easy-invoice'); ?></h3>
970 981 <button type="button" class="close-gallery-modal text-gray-400 hover:text-gray-500 focus:outline-none">
971 - <span class="sr-only"><?php _e('Close', 'easy-invoice'); ?></span>
982 + <span class="sr-only"><?php esc_html_e('Close', 'easy-invoice'); ?></span>
972 983 <i class="fas fa-times text-xl"></i>
973 984 </button>
974 985 </div>
975 986
@@ -979,9 +990,9 @@
979 990 foreach ($all_templates as $template_id => $template) :
980 991 $is_current = ($current_template === $template_id);
981 992 $selected_class = $is_current ? 'selected' : '';
982 993 ?>
983 - <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'; ?>">
994 + <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'; ?>">
984 995 <div class="p-4 border rounded-lg">
985 996 <div class="flex items-center mb-2">
986 997 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
987 998 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
@@ -997,12 +1008,12 @@
997 1008 </div>
998 1009
999 1010 <div class="flex justify-end mt-4 space-x-3">
1000 1011 <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">
1001 - <?php _e('Apply', 'easy-invoice'); ?>
1012 + <?php esc_html_e('Apply', 'easy-invoice'); ?>
1002 1013 </button>
1003 1014 <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">
1004 - <?php _e('Close', 'easy-invoice'); ?>
1015 + <?php esc_html_e('Close', 'easy-invoice'); ?>
1005 1016 </button>
1006 1017 </div>
1007 1018 </div>
1008 1019 </div>
@@ -1015,9 +1026,9 @@
1015 1026 <i class="fas fa-chevron-down text-sm"></i>
1016 1027 </button>
1017 1028 <h3 class="text-base font-normal text-gray-800 flex-1">
1018 1029 <?php
1019 - echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
1030 + echo !empty($title) ? esc_html($title) : esc_html__('New Item', 'easy-invoice');
1020 1031 ?>
1021 1032 </h3>
1022 1033 </div>
1023 1034 <div class="flex items-center gap-3">
@@ -1024,9 +1035,9 @@
1024 1035 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
1025 1036 <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span>
1026 1037 = $<span class="total-summary">0.00</span>
1027 1038 </div>
1028 - <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'); ?>">
1039 + <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')); ?>">
1029 1040 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1030 1041 <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"/>
1031 1042 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
1032 1043 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
@@ -1033,14 +1044,14 @@
1033 1044 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
1034 1045 </svg>
1035 1046 </button>
1036 1047 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
1037 - <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'); ?>">
1048 + <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')); ?>">
1038 1049 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1039 1050 <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"/>
1040 1051 </svg>
1041 1052 </button>
1042 - <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-nonce="<?php echo wp_create_nonce('easy_invoice_pro_item_library'); ?>">
1053 + <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-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_pro_item_library')); ?>">
1043 1054 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1044 1055 <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"/>
1045 1056 </svg>
1046 1057 </button>
@@ -1045,36 +1056,32 @@
1045 1056 </svg>
1046 1057 </button>
1047 1058 <?php endif; ?>
1048 1059 <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">
1049 - <i class="fas fa-trash-alt mr-1"></i> <?php _e('Remove', 'easy-invoice'); ?>
1060 + <i class="fas fa-trash-alt mr-1"></i> <?php esc_html_e('Remove', 'easy-invoice'); ?>
1050 1061 </button>
1051 1062 </div>
1052 1063 </div>
1053 1064 <div class="item-content">
1054 - <?php echo $form_manager->generateItemTemplateHtml(); ?>
1065 + <?php echo $form_manager->generateItemTemplateHtml(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- form markup built by ItemFieldRenderer, which escapes every value. ?>
1055 1066 </div>
1056 1067 </div>
1057 1068 </template>
1058 1069
1059 - <div id="add_client_modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50">
1060 - <div class="relative top-20 mx-auto p-5 border w-11/12 md:w-2/3 lg:w-1/2 shadow-lg rounded-md bg-white">
1061 - <div class="flex justify-between items-center mb-4">
1062 - <h3 class="text-lg font-medium text-gray-900">Add New Client</h3>
1063 - <button type="button" class="close-modal text-gray-400 hover:text-gray-500">
1064 - <span class="sr-only">Close</span>
1065 - <i class="fas fa-times"></i>
1066 - </button>
1067 - </div>
1070 + <?php
1071 + // The "Add New Client" modal used to be duplicated here. This file is
1072 + // included by templates/invoices/builder.php, which renders its own
1073 + // copy of the modal a few lines later — so #add_client_modal existed
1074 + // twice in the DOM. Every `$('#add_client_modal')` binding matched the
1075 + // first one, which was this one: the older markup, missing the
1076 + // role="dialog" / aria-modal / aria-labelledby attributes the builder's
1077 + // copy carries. The accessible modal was inert dead markup.
1078 + //
1079 + // Removed here, keeping the builder's version — which is exactly how
1080 + // the quote side is already structured (quotes/form.php has no modal;
1081 + // quotes/builder.php owns the only one).
1082 + ?>
1068 1083
1069 - <?php
1070 - // Unset the $client variable from the foreach loop to ensure clean add form
1071 - unset($client);
1072 - include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
1073 - ?>
1074 - </div>
1075 - </div>
1076 -
1077 1084 <?php
1078 1085
1079 1086 // Always localize script with AJAX URL and nonce
1080 1087 wp_localize_script('easy-invoice-form', 'easyInvoice', array(
@@ -1267,22 +1274,22 @@
1267 1274 $('#client-search-loading').hide();
1268 1275
1269 1276 if (response.success && response.data.length > 0) {
1270 1277 response.data.forEach(function(client) {
1271 - const option = $(`
1272 - <div class="client-option px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0"
1273 - data-client-id="${client.id}"
1274 - data-client-name="${client.name.replace(/"/g, '&quot;')}"
1275 - data-client-email="${client.email.replace(/"/g, '&quot;')}"
1276 - data-client-company="${(client.company || '').replace(/"/g, '&quot;')}"
1277 - data-client-phone="${(client.phone || '').replace(/"/g, '&quot;')}"
1278 - data-client-website="${(client.website || '').replace(/"/g, '&quot;')}"
1279 - data-client-address="${(client.address || '').replace(/"/g, '&quot;')}"
1280 - onclick="selectClientFromOption(this)">
1281 - <div class="font-medium text-gray-900">${client.name}</div>
1282 - <div class="text-sm text-gray-600">${client.email}</div>
1283 - </div>
1284 - `);
1278 + // Built with jQuery, never by string interpolation: the name, company
1279 + // and address are what the client typed into their portal profile.
1280 + const option = $('<div>', {
1281 + 'class': 'client-option px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0',
1282 + 'data-client-id': client.id,
1283 + 'data-client-name': client.name || '',
1284 + 'data-client-email': client.email || '',
1285 + 'data-client-company': client.company || '',
1286 + 'data-client-phone': client.phone || '',
1287 + 'data-client-website': client.website || '',
1288 + 'data-client-address': client.address || ''
1289 + }).on('click', function() { selectClientFromOption(this); })
1290 + .append($('<div>', { 'class': 'font-medium text-gray-900' }).text(client.name || ''))
1291 + .append($('<div>', { 'class': 'text-sm text-gray-600' }).text(client.email || ''));
1285 1292
1286 1293 $('#client-search-list').append(option);
1287 1294 });
1288 1295 $('#client-search-results').show();
@@ -1310,9 +1317,10 @@
1310 1317 $('#client-info-display').show().removeClass('hidden');
1311 1318 $('#no-client-message').hide().removeClass('hidden');
1312 1319
1313 1320 // Update the edit client button URL
1314 - $('#edit-selected-client').attr('href', '<?php echo admin_url('admin.php?page=easy-invoice-client-edit&client_id='); ?>' + clientId);
1321 + $('#edit-selected-client').attr('href', '<?php echo esc_url(admin_url('admin.php?page=easy-invoice-client-edit&client_id=')); ?>' + clientId);
1322 + $('#edit-selected-client').toggleClass('hidden', !clientId);
1315 1323
1316 1324 // If we have full client data, populate all fields directly
1317 1325 if (clientData) {
1318 1326 $('#display-client-name').text(clientData.name || clientName || '-');
@@ -1374,18 +1382,18 @@
1374 1382 <script>
1375 1383 // Invoice field configuration and pre-loaded data
1376 1384 window.easyInvoice = window.easyInvoice || {};
1377 1385 <?php
1378 -$json_field_config = json_encode($invoice_field_config);
1386 +$json_field_config = wp_json_encode($invoice_field_config);
1379 1387 ?>
1380 -window.easyInvoice.fieldConfig = <?php echo $json_field_config; ?>;
1381 -window.easyInvoice.adminUrl = '<?php echo admin_url(); ?>';
1388 +window.easyInvoice.fieldConfig = <?php echo $json_field_config; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_json_encode() output. ?>;
1389 +window.easyInvoice.adminUrl = '<?php echo esc_url(admin_url()); ?>';
1382 1390 window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>;
1383 1391 window.easyInvoice.translations = {
1384 - premiumTemplates: '<?php _e('Premium Templates', 'easy-invoice'); ?>',
1385 - premiumTemplatesDescription: '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.', 'easy-invoice'); ?>',
1386 - premiumFeatureMessage: '<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>',
1387 - selectTemplateFirst: '<?php _e('Please select a template first', 'easy-invoice'); ?>'
1392 + premiumTemplates: '<?php echo esc_js(__('Premium Templates', 'easy-invoice')); ?>',
1393 + premiumTemplatesDescription: '<?php echo esc_js(__('Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.', 'easy-invoice')); ?>',
1394 + premiumFeatureMessage: '<?php echo esc_js(__('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice')); ?>',
1395 + selectTemplateFirst: '<?php echo esc_js(__('Please select a template first', 'easy-invoice')); ?>'
1388 1396 };
1389 1397 <?php if ($client_data): ?>
1390 1398 window.easyInvoice.clientData = <?php echo json_encode($client_data); ?>;
1391 1399 <?php endif; ?>