PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.2
2.4.3 2.4.4 2.4.2 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 All 60 releases
← All changes | templates/invoices/form.php +170 -122 2.3.1 → 2.4.2 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
@@ -179,12 +182,52 @@
179 182 </button>
180 183 <?php endforeach; ?>
181 184 </nav>
182 185 </div>
183 - <form id="invoice-form">
186 + <?php
187 + // Lifecycle-stage lock check. Addons (PartialPayments)
188 + // can return false here to mark this invoice as
189 + // immutable — currently used to freeze paid deposit
190 + // invoices so admins don't accidentally invalidate
191 + // the linked balance pair. Default: true (no listener).
192 + $ei_can_edit = ! $invoice_id || apply_filters('easy_invoice_can_edit_invoice', true, (int) $invoice_id);
193 + if (! $ei_can_edit && $invoice_id):
194 + $ei_balance_id = (int) get_post_meta((int) $invoice_id, '_balance_invoice_id', true);
195 + $ei_paid_date = (string) get_post_meta((int) $invoice_id, '_easy_invoice_payment_date', true);
196 + ?>
197 + <div class="mx-6 mt-4 mb-4 rounded-md border border-amber-200 bg-amber-50 p-4">
198 + <div class="flex items-start">
199 + <i class="fas fa-lock text-amber-500 mt-0.5 mr-3"></i>
200 + <div class="flex-1 text-sm text-amber-800">
201 + <p class="font-semibold mb-1"><?php esc_html_e('This deposit invoice is locked.', 'easy-invoice'); ?></p>
202 + <p class="mb-2">
203 + <?php
204 + if ($ei_paid_date) {
205 + printf(
206 + /* translators: %s: payment date */
207 + esc_html__('A payment was recorded on %s. Items and totals are frozen so the deposit receipt the customer already saw stays accurate. To add new charges, open the linked balance invoice instead.', 'easy-invoice'),
208 + esc_html($ei_paid_date)
209 + );
210 + } else {
211 + esc_html_e('A payment has been recorded against this deposit. Items and totals are frozen so the deposit receipt the customer already saw stays accurate. To add new charges, open the linked balance invoice instead.', 'easy-invoice');
212 + }
213 + ?>
214 + </p>
215 + <?php if ($ei_balance_id > 0): ?>
216 + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $ei_balance_id)); ?>"
217 + class="inline-flex items-center px-3 py-1.5 rounded text-xs font-medium bg-amber-600 text-white hover:bg-amber-700">
218 + <i class="fas fa-arrow-right mr-1"></i>
219 + <?php esc_html_e('Open the balance invoice', 'easy-invoice'); ?>
220 + </a>
221 + <?php endif; ?>
222 + </div>
223 + </div>
224 + </div>
225 + <?php endif; ?>
226 + <form id="invoice-form"<?php echo $ei_can_edit ? '' : ' data-ei-locked="1"'; ?>>
184 227 <input type="hidden" id="invoice-id" name="invoice_id" value="<?php echo $invoice_id ? esc_attr($invoice_id) : ''; ?>">
185 228
186 - <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')); ?>">
187 230
188 231 <?php foreach ($tabs as $tab): ?>
189 232 <div class="tab-content <?php echo $tab['active'] ? 'active' : 'hidden'; ?> px-6 py-5" id="<?php echo esc_attr($tab['id']); ?>">
190 233 <?php if ($tab['id'] === 'invoice-tab'): ?>
@@ -189,11 +232,11 @@
189 232 <div class="tab-content <?php echo $tab['active'] ? 'active' : 'hidden'; ?> px-6 py-5" id="<?php echo esc_attr($tab['id']); ?>">
190 233 <?php if ($tab['id'] === 'invoice-tab'): ?>
191 234 <div class="mb-8 pb-6 border-b border-gray-200">
192 235 <div class="flex justify-between items-center mb-4">
193 - <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>
194 237 <button type="button" id="browse-all-templates" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
195 - <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>
196 239 <i class="fas fa-chevron-right"></i>
197 240 </button>
198 241 </div>
199 242
@@ -263,10 +306,12 @@
263 306 if ($is_free_version && $is_premium) {
264 307 $premium_classes = 'premium-template-blurred';
265 308 }
266 309 ?>
267 - <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'; ?>">
268 - <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' : ''); ?>>
269 314 <div class="p-4 border rounded-lg">
270 315 <div class="flex items-center mb-2">
271 316 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
272 317 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
@@ -288,9 +333,9 @@
288 333 foreach ($main_fields as $field):
289 334 // Skip fields that have a section (like notes) - they're rendered separately
290 335 // Also skip the template field as it's rendered manually in the template selection section
291 336 if (!isset($field['section']) && $field['name'] !== 'invoice_template') {
292 - 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.
293 338 }
294 339 endforeach;
295 340 ?>
296 341 </div>
@@ -302,9 +347,9 @@
302 347 <i class="fas fa-info-circle text-blue-400"></i>
303 348 </div>
304 349 <div class="ml-3">
305 350 <p class="text-sm text-blue-700">
306 - <?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'); ?>
307 352 </p>
308 353 </div>
309 354 </div>
310 355 </div>
@@ -315,13 +360,13 @@
315 360 $recurring_fields = $form_manager->getFieldsBySection('invoice-tab', 'recurring');
316 361 if (!empty($recurring_fields)):
317 362 ?>
318 363 <div class="mt-8 border-t border-gray-200 pt-6">
319 - <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>
320 365 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
321 366 <?php
322 367 foreach ($recurring_fields as $field):
323 - 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.
324 369 endforeach;
325 370 ?>
326 371 </div>
327 372 </div>
@@ -335,27 +380,27 @@
335 380 <i class="fas fa-sync-alt text-white text-sm"></i>
336 381 </div>
337 382 </div>
338 383 <div class="ml-3 flex-1">
339 - <h3 class="text-base font-semibold text-gray-900 mb-1"><?php _e('Recurring Invoices', 'easy-invoice'); ?></h3>
340 - <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>
341 386 <div class="flex items-center space-x-3 mb-3">
342 387 <div class="flex items-center text-xs text-gray-500">
343 388 <i class="fas fa-check text-green-500 mr-1"></i>
344 - <span><?php _e('Automated billing cycles', 'easy-invoice'); ?></span>
389 + <span><?php esc_html_e('Automated billing cycles', 'easy-invoice'); ?></span>
345 390 </div>
346 391 <div class="flex items-center text-xs text-gray-500">
347 392 <i class="fas fa-check text-green-500 mr-1"></i>
348 - <span><?php _e('Custom intervals', 'easy-invoice'); ?></span>
393 + <span><?php esc_html_e('Custom intervals', 'easy-invoice'); ?></span>
349 394 </div>
350 395 <div class="flex items-center text-xs text-gray-500">
351 396 <i class="fas fa-check text-green-500 mr-1"></i>
352 - <span><?php _e('Auto-send notifications', 'easy-invoice'); ?></span>
397 + <span><?php esc_html_e('Auto-send notifications', 'easy-invoice'); ?></span>
353 398 </div>
354 399 </div>
355 - <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')); ?>'); }">
356 401 <i class="fas fa-crown mr-1.5"></i>
357 - <?php _e('Upgrade to Pro', 'easy-invoice'); ?>
402 + <?php esc_html_e('Upgrade to Pro', 'easy-invoice'); ?>
358 403 </button>
359 404 </div>
360 405 </div>
361 406 </div>
@@ -370,13 +415,13 @@
370 415 $subscription_fields = $form_manager->getFieldsBySection('invoice-tab', 'subscription');
371 416 if (!empty($subscription_fields)):
372 417 ?>
373 418 <div class="mt-8 border-t border-gray-200 pt-6">
374 - <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>
375 420 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
376 421 <?php
377 422 foreach ($subscription_fields as $field):
378 - 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.
379 424 endforeach;
380 425 ?>
381 426 </div>
382 427 </div>
@@ -391,27 +436,27 @@
391 436 <i class="fas fa-calendar-alt text-white text-sm"></i>
392 437 </div>
393 438 </div>
394 439 <div class="ml-3 flex-1">
395 - <h3 class="text-base font-semibold text-gray-900 mb-1"><?php _e('Partial Payment', 'easy-invoice'); ?></h3>
396 - <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>
397 442 <div class="flex items-center space-x-3 mb-3">
398 443 <div class="flex items-center text-xs text-gray-500">
399 444 <i class="fas fa-check text-green-500 mr-1"></i>
400 - <span><?php _e('Flexible payment schedules', 'easy-invoice'); ?></span>
445 + <span><?php esc_html_e('Flexible payment schedules', 'easy-invoice'); ?></span>
401 446 </div>
402 447 <div class="flex items-center text-xs text-gray-500">
403 448 <i class="fas fa-check text-green-500 mr-1"></i>
404 - <span><?php _e('Payment tracking', 'easy-invoice'); ?></span>
449 + <span><?php esc_html_e('Payment tracking', 'easy-invoice'); ?></span>
405 450 </div>
406 451 <div class="flex items-center text-xs text-gray-500">
407 452 <i class="fas fa-check text-green-500 mr-1"></i>
408 - <span><?php _e('Balance management', 'easy-invoice'); ?></span>
453 + <span><?php esc_html_e('Balance management', 'easy-invoice'); ?></span>
409 454 </div>
410 455 </div>
411 - <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')); ?>'); }">
412 457 <i class="fas fa-crown mr-1.5"></i>
413 - <?php _e('Upgrade to Pro', 'easy-invoice'); ?>
458 + <?php esc_html_e('Upgrade to Pro', 'easy-invoice'); ?>
414 459 </button>
415 460 </div>
416 461 </div>
417 462 </div>
@@ -418,15 +463,15 @@
418 463 </div>
419 464 <?php endif; ?>
420 465
421 466 <div class="mt-8 border-t border-gray-200 pt-6">
422 - <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>
423 468 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
424 469 <?php
425 470 // Only render notes and terms fields, not all invoice tab fields
426 471 $notes_fields = $form_manager->getFieldsBySection('invoice-tab', 'notes');
427 472 foreach ($notes_fields as $field):
428 - 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.
429 474 endforeach;
430 475 ?>
431 476 </div>
432 477 </div>
@@ -432,12 +477,12 @@
432 477 </div>
433 478
434 479 <?php elseif ($tab['id'] === 'items-tab'): ?>
435 480 <div class="flex justify-between items-center mb-6">
436 - <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>
437 482 <button type="button" id="collapse-all-items" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
438 483 <i class="fas fa-chevron-down mr-1"></i>
439 - <span><?php _e('Collapse All', 'easy-invoice'); ?></span>
484 + <span><?php esc_html_e('Collapse All', 'easy-invoice'); ?></span>
440 485 </button>
441 486 </div>
442 487
443 488 <div class="space-y-4 invoice-items-container">
@@ -446,9 +491,9 @@
446 491 if ($invoice && $invoice->getItems()) :
447 492 $index = 0;
448 493 foreach ($invoice->getItems() as $item) :
449 494 // Generate a unique ID for the item
450 - $item_id = 'item_' . time() . '_' . $index . '_' . rand(1000, 9999);
495 + $item_id = 'item_' . time() . '_' . $index . '_' . wp_rand(1000, 9999);
451 496
452 497 // Get display title for header using dynamic field access
453 498 $title = $item->getName();
454 499
@@ -459,12 +504,13 @@
459 504 $title = $item_data['title'];
460 505 }
461 506 }
462 507
508 + /* translators: %d: title. */
463 509 $display_title = !empty($title) ? $title : sprintf(__('Item #%d', 'easy-invoice'), $index + 1);
464 510 $short_title = strlen($display_title) > 30 ? substr($display_title, 0, 30) . '...' : $display_title;
465 511 ?>
466 - <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; ?>">
467 513 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
468 514 <div class="flex items-center">
469 515 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
470 516 <i class="fas fa-chevron-down text-sm"></i>
@@ -470,9 +516,9 @@
470 516 <i class="fas fa-chevron-down text-sm"></i>
471 517 </button>
472 518 <h3 class="text-base font-normal text-gray-800 flex-1">
473 519 <?php
474 - echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
520 + echo !empty($title) ? esc_html($title) : esc_html__('New Item', 'easy-invoice');
475 521 ?>
476 522 </h3>
477 523 </div>
478 524 <div class="flex items-center gap-3">
@@ -479,9 +525,9 @@
479 525 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
480 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>
481 527 = $<span class="total-summary font-medium"><?php echo esc_html(number_format($item->getAmount(), 2)); ?></span>
482 528 </div>
483 - <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')); ?>">
484 530 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
485 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"/>
486 532 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
487 533 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
@@ -488,14 +534,14 @@
488 534 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
489 535 </svg>
490 536 </button>
491 537 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
492 - <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')); ?>">
493 539 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
494 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"/>
495 541 </svg>
496 542 </button>
497 - <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')); ?>">
498 544 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
499 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"/>
500 546 </svg>
501 547 </button>
@@ -500,14 +546,15 @@
500 546 </svg>
501 547 </button>
502 548 <?php endif; ?>
503 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">
504 - <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'); ?>
505 551 </button>
506 552 </div>
507 553 </div>
508 554 <div class="item-content">
509 - <?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 +?>
510 557 </div>
511 558 </div>
512 559 <?php
513 560 $index++;
@@ -543,9 +590,9 @@
543 590 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
544 591 <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span>
545 592 = $<span class="total-summary">0.00</span>
546 593 </div>
547 - <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')); ?>">
548 595 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
549 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"/>
550 597 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
551 598 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
@@ -552,14 +599,14 @@
552 599 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
553 600 </svg>
554 601 </button>
555 602 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
556 - <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')); ?>">
557 604 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
558 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"/>
559 606 </svg>
560 607 </button>
561 - <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')); ?>">
562 609 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
563 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"/>
564 611 </svg>
565 612 </button>
@@ -564,14 +611,14 @@
564 611 </svg>
565 612 </button>
566 613 <?php endif; ?>
567 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">
568 - <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'); ?>
569 616 </button>
570 617 </div>
571 618 </div>
572 619 <div class="item-content">
573 - <?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. ?>
574 621 </div>
575 622 </div>
576 623 <?php endif; ?>
577 624 </div>
@@ -582,10 +629,10 @@
582 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">
583 630 <i class="fas fa-plus text-indigo-600 text-lg"></i>
584 631 </div>
585 632 <div class="text-left">
586 - <span class="font-semibold text-base"><?php _e('Add Another Item', 'easy-invoice'); ?></span>
587 - <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>
588 635 </div>
589 636 </div>
590 637 </button>
591 638 </div>
@@ -590,9 +637,9 @@
590 637 </button>
591 638 </div>
592 639 <?php elseif ($tab['id'] === 'client-tab'): ?>
593 640 <div class="mb-6">
594 - <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>
595 642
596 643 <div class="relative">
597 644 <div class="flex items-center space-x-3">
598 645 <div class="relative flex-grow">
@@ -602,15 +649,15 @@
602 649 <input
603 650 type="text"
604 651 id="client-search-input"
605 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"
606 - 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')); ?>"
607 654 autocomplete="off"
608 655 >
609 656 </div>
610 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">
611 658 <i class="fas fa-plus -ml-1 mr-2"></i>
612 - <?php _e('New Client', 'easy-invoice'); ?>
659 + <?php esc_html_e('New Client', 'easy-invoice'); ?>
613 660 </button>
614 661 </div>
615 662
616 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;">
@@ -615,13 +662,13 @@
615 662
616 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;">
617 664 <div id="client-search-loading" class="p-6 text-center text-gray-500" style="display: none;">
618 665 <i class="fas fa-spinner fa-spin mr-2"></i>
619 - <?php _e('Searching...', 'easy-invoice'); ?>
666 + <?php esc_html_e('Searching...', 'easy-invoice'); ?>
620 667 </div>
621 668 <div id="client-search-empty" class="p-6 text-center text-gray-500" style="display: none;">
622 669 <i class="fas fa-search mr-2"></i>
623 - <?php _e('No clients found', 'easy-invoice'); ?>
670 + <?php esc_html_e('No clients found', 'easy-invoice'); ?>
624 671 </div>
625 672 <div id="client-search-list" class="py-2">
626 673 </div>
627 674 </div>
@@ -672,14 +719,18 @@
672 719 </div>
673 720 </div>
674 721
675 722 <select id="select-client" class="hidden">
676 - <option value=""><?php _e('-- Select a client --', 'easy-invoice'); ?></option>
723 + <option value=""><?php esc_html_e('-- Select a client --', 'easy-invoice'); ?></option>
677 724 <?php
678 725 // Ensure $clients is defined
679 - if (!isset($clients)) {
680 - $client_repository = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository();
681 - $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 + }
682 733 }
683 734
684 735 // Output client options
685 736 if (!empty($clients)) {
@@ -705,9 +756,9 @@
705 756 $selected = $invoice && $invoice->getClientId() == $client->getId() ? 'selected' : '';
706 757 printf(
707 758 '<option value="%s" %s data-name="%s" data-email="%s">%s (%s)</option>',
708 759 esc_attr($client->getId()),
709 - $selected,
760 + $selected, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output of selected( ..., false ).
710 761 esc_attr($client_name),
711 762 esc_attr($client_email),
712 763 esc_html($client_name),
713 764 esc_html($client_email)
@@ -725,16 +776,16 @@
725 776 <div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
726 777 <div class="flex items-center justify-between pb-4 border-b border-gray-200 mb-4">
727 778 <h4 class="text-lg font-semibold text-gray-800 flex items-center">
728 779 <i class="fas fa-user-check text-indigo-500 mr-3"></i>
729 - <span><?php _e('Client Information', 'easy-invoice'); ?></span>
780 + <span><?php esc_html_e('Client Information', 'easy-invoice'); ?></span>
730 781 </h4>
731 - <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() : ''))); ?>"
732 783 id="edit-selected-client"
733 - 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'; ?>"
734 785 target="_blank">
735 786 <i class="fas fa-pen mr-1"></i>
736 - <?php _e('Edit', 'easy-invoice'); ?>
787 + <?php esc_html_e('Edit', 'easy-invoice'); ?>
737 788 </a>
738 789 </div>
739 790
740 791 <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2">
@@ -740,9 +791,9 @@
740 791 <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2">
741 792 <div class="flex items-start group">
742 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>
743 794 <div class="ml-3">
744 - <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>
745 796 <p id="display-client-name" class="text-sm font-semibold text-gray-800 mt-1">
746 797 <?php
747 798 if ($invoice && $invoice->getClientId()) {
748 799 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -761,9 +812,9 @@
761 812
762 813 <div class="flex items-start group">
763 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>
764 815 <div class="ml-3">
765 - <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>
766 817 <p id="display-client-company" class="text-sm font-semibold text-gray-800 mt-1">
767 818 <?php
768 819 if ($invoice && $invoice->getClientId()) {
769 820 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -782,9 +833,9 @@
782 833
783 834 <div class="flex items-start group">
784 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>
785 836 <div class="ml-3">
786 - <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>
787 838 <p id="display-client-email" class="text-sm text-gray-800 mt-1 break-all">
788 839 <?php
789 840 if ($invoice && $invoice->getClientId()) {
790 841 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -803,9 +854,9 @@
803 854
804 855 <div class="flex items-start group">
805 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>
806 857 <div class="ml-3">
807 - <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>
808 859 <p id="display-client-phone" class="text-sm text-gray-800 mt-1">
809 860 <?php
810 861 if ($invoice && $invoice->getClientId()) {
811 862 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -824,9 +875,9 @@
824 875
825 876 <div class="flex items-start group md:col-span-2">
826 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>
827 878 <div class="ml-3">
828 - <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>
829 880 <p id="display-client-website" class="text-sm text-gray-800 mt-1">
830 881 <?php
831 882 if ($invoice && $invoice->getClientId()) {
832 883 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -845,9 +896,9 @@
845 896
846 897 <div class="flex items-start group md:col-span-2">
847 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>
848 899 <div class="ml-3">
849 - <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>
850 901 <p id="display-client-address" class="text-sm text-gray-800 mt-1 whitespace-pre-wrap">
851 902 <?php
852 903 if ($invoice && $invoice->getClientId()) {
853 904 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -867,14 +918,14 @@
867 918 </div>
868 919 </div>
869 920
870 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' : ''; ?>">
871 - <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>
872 923 </div>
873 924 </div>
874 925 <?php elseif ($tab['id'] === 'discounts-taxes-tab'): ?>
875 926 <div>
876 - <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>
877 928 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
878 929 <?php
879 930 // Get all discount and tax fields
880 931 $discount_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'discount');
@@ -887,9 +938,9 @@
887 938 return ($a['order'] ?? 0) - ($b['order'] ?? 0);
888 939 });
889 940
890 941 foreach ($all_fields as $field):
891 - 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.
892 943 endforeach;
893 944 ?>
894 945 </div>
895 946 </div>
@@ -894,25 +945,25 @@
894 945 </div>
895 946 </div>
896 947 <?php elseif ($tab['id'] === 'payments-tab'): ?>
897 948 <div>
898 - <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>
899 950 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
900 951 <?php
901 952 $payment_fields = $form_manager->getFieldsBySection('payments-tab', 'payment');
902 953 foreach ($payment_fields as $field):
903 - 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.
904 955 endforeach;
905 956 ?>
906 957 </div>
907 958
908 959 <div class="mt-8 border-t border-gray-200 pt-6">
909 - <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>
910 961 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
911 962 <?php
912 963 $currency_fields = $form_manager->getFieldsBySection('payments-tab', 'currency');
913 964 foreach ($currency_fields as $field):
914 - 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.
915 966 endforeach;
916 967 ?>
917 968 </div>
918 969 </div>
@@ -925,11 +976,11 @@
925 976
926 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">
927 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">
928 979 <div class="flex justify-between items-center mb-4">
929 - <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>
930 981 <button type="button" class="close-gallery-modal text-gray-400 hover:text-gray-500 focus:outline-none">
931 - <span class="sr-only"><?php _e('Close', 'easy-invoice'); ?></span>
982 + <span class="sr-only"><?php esc_html_e('Close', 'easy-invoice'); ?></span>
932 983 <i class="fas fa-times text-xl"></i>
933 984 </button>
934 985 </div>
935 986
@@ -939,9 +990,9 @@
939 990 foreach ($all_templates as $template_id => $template) :
940 991 $is_current = ($current_template === $template_id);
941 992 $selected_class = $is_current ? 'selected' : '';
942 993 ?>
943 - <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'; ?>">
944 995 <div class="p-4 border rounded-lg">
945 996 <div class="flex items-center mb-2">
946 997 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
947 998 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
@@ -957,12 +1008,12 @@
957 1008 </div>
958 1009
959 1010 <div class="flex justify-end mt-4 space-x-3">
960 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">
961 - <?php _e('Apply', 'easy-invoice'); ?>
1012 + <?php esc_html_e('Apply', 'easy-invoice'); ?>
962 1013 </button>
963 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">
964 - <?php _e('Close', 'easy-invoice'); ?>
1015 + <?php esc_html_e('Close', 'easy-invoice'); ?>
965 1016 </button>
966 1017 </div>
967 1018 </div>
968 1019 </div>
@@ -975,9 +1026,9 @@
975 1026 <i class="fas fa-chevron-down text-sm"></i>
976 1027 </button>
977 1028 <h3 class="text-base font-normal text-gray-800 flex-1">
978 1029 <?php
979 - echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
1030 + echo !empty($title) ? esc_html($title) : esc_html__('New Item', 'easy-invoice');
980 1031 ?>
981 1032 </h3>
982 1033 </div>
983 1034 <div class="flex items-center gap-3">
@@ -984,9 +1035,9 @@
984 1035 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
985 1036 <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span>
986 1037 = $<span class="total-summary">0.00</span>
987 1038 </div>
988 - <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')); ?>">
989 1040 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
990 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"/>
991 1042 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
992 1043 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
@@ -993,14 +1044,14 @@
993 1044 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
994 1045 </svg>
995 1046 </button>
996 1047 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
997 - <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')); ?>">
998 1049 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
999 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"/>
1000 1051 </svg>
1001 1052 </button>
1002 - <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')); ?>">
1003 1054 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1004 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"/>
1005 1056 </svg>
1006 1057 </button>
@@ -1005,36 +1056,32 @@
1005 1056 </svg>
1006 1057 </button>
1007 1058 <?php endif; ?>
1008 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">
1009 - <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'); ?>
1010 1061 </button>
1011 1062 </div>
1012 1063 </div>
1013 1064 <div class="item-content">
1014 - <?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. ?>
1015 1066 </div>
1016 1067 </div>
1017 1068 </template>
1018 1069
1019 - <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">
1020 - <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">
1021 - <div class="flex justify-between items-center mb-4">
1022 - <h3 class="text-lg font-medium text-gray-900">Add New Client</h3>
1023 - <button type="button" class="close-modal text-gray-400 hover:text-gray-500">
1024 - <span class="sr-only">Close</span>
1025 - <i class="fas fa-times"></i>
1026 - </button>
1027 - </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 + ?>
1028 1083
1029 - <?php
1030 - // Unset the $client variable from the foreach loop to ensure clean add form
1031 - unset($client);
1032 - include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
1033 - ?>
1034 - </div>
1035 - </div>
1036 -
1037 1084 <?php
1038 1085
1039 1086 // Always localize script with AJAX URL and nonce
1040 1087 wp_localize_script('easy-invoice-form', 'easyInvoice', array(
@@ -1227,22 +1274,22 @@
1227 1274 $('#client-search-loading').hide();
1228 1275
1229 1276 if (response.success && response.data.length > 0) {
1230 1277 response.data.forEach(function(client) {
1231 - const option = $(`
1232 - <div class="client-option px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0"
1233 - data-client-id="${client.id}"
1234 - data-client-name="${client.name.replace(/"/g, '&quot;')}"
1235 - data-client-email="${client.email.replace(/"/g, '&quot;')}"
1236 - data-client-company="${(client.company || '').replace(/"/g, '&quot;')}"
1237 - data-client-phone="${(client.phone || '').replace(/"/g, '&quot;')}"
1238 - data-client-website="${(client.website || '').replace(/"/g, '&quot;')}"
1239 - data-client-address="${(client.address || '').replace(/"/g, '&quot;')}"
1240 - onclick="selectClientFromOption(this)">
1241 - <div class="font-medium text-gray-900">${client.name}</div>
1242 - <div class="text-sm text-gray-600">${client.email}</div>
1243 - </div>
1244 - `);
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 || ''));
1245 1292
1246 1293 $('#client-search-list').append(option);
1247 1294 });
1248 1295 $('#client-search-results').show();
@@ -1270,9 +1317,10 @@
1270 1317 $('#client-info-display').show().removeClass('hidden');
1271 1318 $('#no-client-message').hide().removeClass('hidden');
1272 1319
1273 1320 // Update the edit client button URL
1274 - $('#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);
1275 1323
1276 1324 // If we have full client data, populate all fields directly
1277 1325 if (clientData) {
1278 1326 $('#display-client-name').text(clientData.name || clientName || '-');
@@ -1334,18 +1382,18 @@
1334 1382 <script>
1335 1383 // Invoice field configuration and pre-loaded data
1336 1384 window.easyInvoice = window.easyInvoice || {};
1337 1385 <?php
1338 -$json_field_config = json_encode($invoice_field_config);
1386 +$json_field_config = wp_json_encode($invoice_field_config);
1339 1387 ?>
1340 -window.easyInvoice.fieldConfig = <?php echo $json_field_config; ?>;
1341 -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()); ?>';
1342 1390 window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>;
1343 1391 window.easyInvoice.translations = {
1344 - premiumTemplates: '<?php _e('Premium Templates', 'easy-invoice'); ?>',
1345 - premiumTemplatesDescription: '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.', 'easy-invoice'); ?>',
1346 - premiumFeatureMessage: '<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>',
1347 - 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')); ?>'
1348 1396 };
1349 1397 <?php if ($client_data): ?>
1350 1398 window.easyInvoice.clientData = <?php echo json_encode($client_data); ?>;
1351 1399 <?php endif; ?>