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 +174 -157 2.1.132.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
@@ -168,9 +171,9 @@
168 171
169 172 <div class="bg-white shadow rounded-lg">
170 173
171 174 <div class="border-b border-gray-200">
172 - <nav class="flex -mb-px">
175 + <nav class="flex -mb-px" aria-label="<?php esc_attr_e( 'Invoice form sections', 'easy-invoice' ); ?>">
173 176 <?php foreach ($tabs as $tab): ?>
174 177 <button type="button"
175 178 class="tab-button whitespace-nowrap py-3 px-6 border-b-2 <?php echo $tab['active'] ? 'border-indigo-500 font-medium text-sm text-indigo-600' : 'border-transparent font-medium text-sm text-gray-500 hover:text-gray-700 hover:border-gray-300'; ?> flex flex-col items-center"
176 179 data-tab="<?php echo esc_attr($tab['id']); ?>">
@@ -179,24 +182,61 @@
179 182 </button>
180 183 <?php endforeach; ?>
181 184 </nav>
182 185 </div>
183 - <form id="invoice-form">
184 - <!-- Hidden invoice ID field for edit mode -->
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"'; ?>>
185 227 <input type="hidden" id="invoice-id" name="invoice_id" value="<?php echo $invoice_id ? esc_attr($invoice_id) : ''; ?>">
186 228
187 - <!-- Security nonce -->
188 - <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')); ?>">
189 230
190 231 <?php foreach ($tabs as $tab): ?>
191 232 <div class="tab-content <?php echo $tab['active'] ? 'active' : 'hidden'; ?> px-6 py-5" id="<?php echo esc_attr($tab['id']); ?>">
192 233 <?php if ($tab['id'] === 'invoice-tab'): ?>
193 - <!-- Template Selection Section -->
194 234 <div class="mb-8 pb-6 border-b border-gray-200">
195 235 <div class="flex justify-between items-center mb-4">
196 - <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>
197 237 <button type="button" id="browse-all-templates" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
198 - <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>
199 239 <i class="fas fa-chevron-right"></i>
200 240 </button>
201 241 </div>
202 242
@@ -266,10 +306,12 @@
266 306 if ($is_free_version && $is_premium) {
267 307 $premium_classes = 'premium-template-blurred';
268 308 }
269 309 ?>
270 - <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'; ?>">
271 - <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' : ''); ?>>
272 314 <div class="p-4 border rounded-lg">
273 315 <div class="flex items-center mb-2">
274 316 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
275 317 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
@@ -284,9 +326,8 @@
284 326 <?php endforeach; ?>
285 327 </div>
286 328 </div>
287 329
288 - <!-- Dynamic Fields Section -->
289 330 <div class="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
290 331 <?php
291 332 $main_fields = $form_manager->getFields('invoice-tab');
292 333 foreach ($main_fields as $field):
@@ -292,15 +333,14 @@
292 333 foreach ($main_fields as $field):
293 334 // Skip fields that have a section (like notes) - they're rendered separately
294 335 // Also skip the template field as it's rendered manually in the template selection section
295 336 if (!isset($field['section']) && $field['name'] !== 'invoice_template') {
296 - 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.
297 338 }
298 339 endforeach;
299 340 ?>
300 341 </div>
301 342
302 - <!-- Auto-generation notice for new invoices -->
303 343 <?php if (!$invoice): ?>
304 344 <div class="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-md">
305 345 <div class="flex">
306 346 <div class="flex-shrink-0">
@@ -307,9 +347,9 @@
307 347 <i class="fas fa-info-circle text-blue-400"></i>
308 348 </div>
309 349 <div class="ml-3">
310 350 <p class="text-sm text-blue-700">
311 - <?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'); ?>
312 352 </p>
313 353 </div>
314 354 </div>
315 355 </div>
@@ -314,9 +354,8 @@
314 354 </div>
315 355 </div>
316 356 <?php endif; ?>
317 357
318 - <!-- Recurring Invoice Section -->
319 358 <?php if (easy_invoice_has_pro()): ?>
320 359 <?php
321 360 $recurring_fields = $form_manager->getFieldsBySection('invoice-tab', 'recurring');
322 361 if (!empty($recurring_fields)):
@@ -321,13 +360,13 @@
321 360 $recurring_fields = $form_manager->getFieldsBySection('invoice-tab', 'recurring');
322 361 if (!empty($recurring_fields)):
323 362 ?>
324 363 <div class="mt-8 border-t border-gray-200 pt-6">
325 - <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>
326 365 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
327 366 <?php
328 367 foreach ($recurring_fields as $field):
329 - 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.
330 369 endforeach;
331 370 ?>
332 371 </div>
333 372 </div>
@@ -332,9 +371,8 @@
332 371 </div>
333 372 </div>
334 373 <?php endif; ?>
335 374 <?php else: ?>
336 - <!-- Recurring Invoice Promotion for Free Users -->
337 375 <div class="mt-6 border-t border-gray-200 pt-4">
338 376 <div class="bg-gradient-to-r from-purple-50 to-indigo-50 border border-purple-200 rounded-lg p-4">
339 377 <div class="flex items-start">
340 378 <div class="flex-shrink-0">
@@ -342,27 +380,27 @@
342 380 <i class="fas fa-sync-alt text-white text-sm"></i>
343 381 </div>
344 382 </div>
345 383 <div class="ml-3 flex-1">
346 - <h3 class="text-base font-semibold text-gray-900 mb-1"><?php _e('Recurring Invoices', 'easy-invoice'); ?></h3>
347 - <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>
348 386 <div class="flex items-center space-x-3 mb-3">
349 387 <div class="flex items-center text-xs text-gray-500">
350 388 <i class="fas fa-check text-green-500 mr-1"></i>
351 - <span><?php _e('Automated billing cycles', 'easy-invoice'); ?></span>
389 + <span><?php esc_html_e('Automated billing cycles', 'easy-invoice'); ?></span>
352 390 </div>
353 391 <div class="flex items-center text-xs text-gray-500">
354 392 <i class="fas fa-check text-green-500 mr-1"></i>
355 - <span><?php _e('Custom intervals', 'easy-invoice'); ?></span>
393 + <span><?php esc_html_e('Custom intervals', 'easy-invoice'); ?></span>
356 394 </div>
357 395 <div class="flex items-center text-xs text-gray-500">
358 396 <i class="fas fa-check text-green-500 mr-1"></i>
359 - <span><?php _e('Auto-send notifications', 'easy-invoice'); ?></span>
397 + <span><?php esc_html_e('Auto-send notifications', 'easy-invoice'); ?></span>
360 398 </div>
361 399 </div>
362 - <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')); ?>'); }">
363 401 <i class="fas fa-crown mr-1.5"></i>
364 - <?php _e('Upgrade to Pro', 'easy-invoice'); ?>
402 + <?php esc_html_e('Upgrade to Pro', 'easy-invoice'); ?>
365 403 </button>
366 404 </div>
367 405 </div>
368 406 </div>
@@ -368,9 +406,8 @@
368 406 </div>
369 407 </div>
370 408 <?php endif; ?>
371 409
372 - <!-- Subscription Invoice Section -->
373 410 <?php if (easy_invoice_has_pro()): ?>
374 411 <?php
375 412 // Check if subscription is actually enabled
376 413 $subscription_enabled = get_option('easy_invoice_pro_subscription_enabled', '0');
@@ -378,13 +415,13 @@
378 415 $subscription_fields = $form_manager->getFieldsBySection('invoice-tab', 'subscription');
379 416 if (!empty($subscription_fields)):
380 417 ?>
381 418 <div class="mt-8 border-t border-gray-200 pt-6">
382 - <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>
383 420 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
384 421 <?php
385 422 foreach ($subscription_fields as $field):
386 - 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.
387 424 endforeach;
388 425 ?>
389 426 </div>
390 427 </div>
@@ -390,9 +427,8 @@
390 427 </div>
391 428 <?php endif; ?>
392 429 <?php endif; ?>
393 430 <?php else: ?>
394 - <!-- Subscription Invoice Promotion for Free Users -->
395 431 <div class="mt-6 border-t border-gray-200 pt-4">
396 432 <div class="bg-gradient-to-r from-purple-50 to-indigo-50 border border-purple-200 rounded-lg p-4">
397 433 <div class="flex items-start">
398 434 <div class="flex-shrink-0">
@@ -400,27 +436,27 @@
400 436 <i class="fas fa-calendar-alt text-white text-sm"></i>
401 437 </div>
402 438 </div>
403 439 <div class="ml-3 flex-1">
404 - <h3 class="text-base font-semibold text-gray-900 mb-1"><?php _e('Partial Payment', 'easy-invoice'); ?></h3>
405 - <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>
406 442 <div class="flex items-center space-x-3 mb-3">
407 443 <div class="flex items-center text-xs text-gray-500">
408 444 <i class="fas fa-check text-green-500 mr-1"></i>
409 - <span><?php _e('Flexible payment schedules', 'easy-invoice'); ?></span>
445 + <span><?php esc_html_e('Flexible payment schedules', 'easy-invoice'); ?></span>
410 446 </div>
411 447 <div class="flex items-center text-xs text-gray-500">
412 448 <i class="fas fa-check text-green-500 mr-1"></i>
413 - <span><?php _e('Payment tracking', 'easy-invoice'); ?></span>
449 + <span><?php esc_html_e('Payment tracking', 'easy-invoice'); ?></span>
414 450 </div>
415 451 <div class="flex items-center text-xs text-gray-500">
416 452 <i class="fas fa-check text-green-500 mr-1"></i>
417 - <span><?php _e('Balance management', 'easy-invoice'); ?></span>
453 + <span><?php esc_html_e('Balance management', 'easy-invoice'); ?></span>
418 454 </div>
419 455 </div>
420 - <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')); ?>'); }">
421 457 <i class="fas fa-crown mr-1.5"></i>
422 - <?php _e('Upgrade to Pro', 'easy-invoice'); ?>
458 + <?php esc_html_e('Upgrade to Pro', 'easy-invoice'); ?>
423 459 </button>
424 460 </div>
425 461 </div>
426 462 </div>
@@ -426,17 +462,16 @@
426 462 </div>
427 463 </div>
428 464 <?php endif; ?>
429 465
430 - <!-- Notes & Terms Section -->
431 466 <div class="mt-8 border-t border-gray-200 pt-6">
432 - <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>
433 468 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
434 469 <?php
435 470 // Only render notes and terms fields, not all invoice tab fields
436 471 $notes_fields = $form_manager->getFieldsBySection('invoice-tab', 'notes');
437 472 foreach ($notes_fields as $field):
438 - 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.
439 474 endforeach;
440 475 ?>
441 476 </div>
442 477 </div>
@@ -441,14 +476,13 @@
441 476 </div>
442 477 </div>
443 478
444 479 <?php elseif ($tab['id'] === 'items-tab'): ?>
445 - <!-- Items Tab Content -->
446 480 <div class="flex justify-between items-center mb-6">
447 - <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>
448 482 <button type="button" id="collapse-all-items" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
449 483 <i class="fas fa-chevron-down mr-1"></i>
450 - <span><?php _e('Collapse All', 'easy-invoice'); ?></span>
484 + <span><?php esc_html_e('Collapse All', 'easy-invoice'); ?></span>
451 485 </button>
452 486 </div>
453 487
454 488 <div class="space-y-4 invoice-items-container">
@@ -457,9 +491,9 @@
457 491 if ($invoice && $invoice->getItems()) :
458 492 $index = 0;
459 493 foreach ($invoice->getItems() as $item) :
460 494 // Generate a unique ID for the item
461 - $item_id = 'item_' . time() . '_' . $index . '_' . rand(1000, 9999);
495 + $item_id = 'item_' . time() . '_' . $index . '_' . wp_rand(1000, 9999);
462 496
463 497 // Get display title for header using dynamic field access
464 498 $title = $item->getName();
465 499
@@ -470,12 +504,13 @@
470 504 $title = $item_data['title'];
471 505 }
472 506 }
473 507
508 + /* translators: %d: title. */
474 509 $display_title = !empty($title) ? $title : sprintf(__('Item #%d', 'easy-invoice'), $index + 1);
475 510 $short_title = strlen($display_title) > 30 ? substr($display_title, 0, 30) . '...' : $display_title;
476 511 ?>
477 - <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; ?>">
478 513 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
479 514 <div class="flex items-center">
480 515 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
481 516 <i class="fas fa-chevron-down text-sm"></i>
@@ -481,9 +516,9 @@
481 516 <i class="fas fa-chevron-down text-sm"></i>
482 517 </button>
483 518 <h3 class="text-base font-normal text-gray-800 flex-1">
484 519 <?php
485 - echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
520 + echo !empty($title) ? esc_html($title) : esc_html__('New Item', 'easy-invoice');
486 521 ?>
487 522 </h3>
488 523 </div>
489 524 <div class="flex items-center gap-3">
@@ -490,9 +525,9 @@
490 525 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
491 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>
492 527 = $<span class="total-summary font-medium"><?php echo esc_html(number_format($item->getAmount(), 2)); ?></span>
493 528 </div>
494 - <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')); ?>">
495 530 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
496 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"/>
497 532 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
498 533 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
@@ -498,15 +533,15 @@
498 533 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
499 534 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
500 535 </svg>
501 536 </button>
502 - <?php if (class_exists('EasyInvoicePro\\Services\\ItemLibraryService')): ?>
503 - <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'); ?>">
537 + <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
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')); ?>">
504 539 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
505 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"/>
506 541 </svg>
507 542 </button>
508 - <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')); ?>">
509 544 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
510 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"/>
511 546 </svg>
512 547 </button>
@@ -511,14 +546,15 @@
511 546 </svg>
512 547 </button>
513 548 <?php endif; ?>
514 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">
515 - <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'); ?>
516 551 </button>
517 552 </div>
518 553 </div>
519 554 <div class="item-content">
520 - <?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 +?>
521 557 </div>
522 558 </div>
523 559 <?php
524 560 $index++;
@@ -554,9 +590,9 @@
554 590 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
555 591 <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span>
556 592 = $<span class="total-summary">0.00</span>
557 593 </div>
558 - <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')); ?>">
559 595 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
560 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"/>
561 597 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
562 598 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
@@ -562,15 +598,15 @@
562 598 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
563 599 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
564 600 </svg>
565 601 </button>
566 - <?php if (class_exists('EasyInvoicePro\\Services\\ItemLibraryService')): ?>
567 - <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'); ?>">
602 + <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
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')); ?>">
568 604 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
569 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"/>
570 606 </svg>
571 607 </button>
572 - <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')); ?>">
573 609 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
574 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"/>
575 611 </svg>
576 612 </button>
@@ -575,14 +611,14 @@
575 611 </svg>
576 612 </button>
577 613 <?php endif; ?>
578 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">
579 - <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'); ?>
580 616 </button>
581 617 </div>
582 618 </div>
583 619 <div class="item-content">
584 - <?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. ?>
585 621 </div>
586 622 </div>
587 623 <?php endif; ?>
588 624 </div>
@@ -593,20 +629,18 @@
593 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">
594 630 <i class="fas fa-plus text-indigo-600 text-lg"></i>
595 631 </div>
596 632 <div class="text-left">
597 - <span class="font-semibold text-base"><?php _e('Add Another Item', 'easy-invoice'); ?></span>
598 - <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>
599 635 </div>
600 636 </div>
601 637 </button>
602 638 </div>
603 639 <?php elseif ($tab['id'] === 'client-tab'): ?>
604 - <!-- Client Tab Content -->
605 640 <div class="mb-6">
606 - <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>
607 642
608 - <!-- Client Search and Selection -->
609 643 <div class="relative">
610 644 <div class="flex items-center space-x-3">
611 645 <div class="relative flex-grow">
612 646 <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
@@ -615,34 +649,31 @@
615 649 <input
616 650 type="text"
617 651 id="client-search-input"
618 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"
619 - 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')); ?>"
620 654 autocomplete="off"
621 655 >
622 656 </div>
623 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">
624 658 <i class="fas fa-plus -ml-1 mr-2"></i>
625 - <?php _e('New Client', 'easy-invoice'); ?>
659 + <?php esc_html_e('New Client', 'easy-invoice'); ?>
626 660 </button>
627 661 </div>
628 662
629 - <!-- Search Results Dropdown -->
630 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;">
631 664 <div id="client-search-loading" class="p-6 text-center text-gray-500" style="display: none;">
632 665 <i class="fas fa-spinner fa-spin mr-2"></i>
633 - <?php _e('Searching...', 'easy-invoice'); ?>
666 + <?php esc_html_e('Searching...', 'easy-invoice'); ?>
634 667 </div>
635 668 <div id="client-search-empty" class="p-6 text-center text-gray-500" style="display: none;">
636 669 <i class="fas fa-search mr-2"></i>
637 - <?php _e('No clients found', 'easy-invoice'); ?>
670 + <?php esc_html_e('No clients found', 'easy-invoice'); ?>
638 671 </div>
639 672 <div id="client-search-list" class="py-2">
640 - <!-- Client options will be populated here -->
641 673 </div>
642 674 </div>
643 675
644 - <!-- Selected Client Display -->
645 676 <div id="selected-client-display" class="mt-4 p-4 bg-gradient-to-r from-indigo-50 to-blue-50 border border-indigo-200 rounded-lg shadow-sm <?php echo ($invoice && $invoice->getClientId()) ? '' : 'hidden'; ?>">
646 677 <div class="flex items-center justify-between">
647 678 <div class="flex items-center">
648 679 <div class="flex-shrink-0">
@@ -687,16 +718,19 @@
687 718 </div>
688 719 </div>
689 720 </div>
690 721
691 - <!-- Hidden select for form submission -->
692 722 <select id="select-client" class="hidden">
693 - <option value=""><?php _e('-- Select a client --', 'easy-invoice'); ?></option>
723 + <option value=""><?php esc_html_e('-- Select a client --', 'easy-invoice'); ?></option>
694 724 <?php
695 725 // Ensure $clients is defined
696 - if (!isset($clients)) {
697 - $client_repository = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository();
698 - $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 + }
699 733 }
700 734
701 735 // Output client options
702 736 if (!empty($clients)) {
@@ -722,9 +756,9 @@
722 756 $selected = $invoice && $invoice->getClientId() == $client->getId() ? 'selected' : '';
723 757 printf(
724 758 '<option value="%s" %s data-name="%s" data-email="%s">%s (%s)</option>',
725 759 esc_attr($client->getId()),
726 - $selected,
760 + $selected, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output of selected( ..., false ).
727 761 esc_attr($client_name),
728 762 esc_attr($client_email),
729 763 esc_html($client_name),
730 764 esc_html($client_email)
@@ -737,31 +771,29 @@
737 771
738 772 <div class="border-t border-gray-200 pt-4 mt-4">
739 773 <input type="hidden" name="client_id" id="client-id" value="<?php echo $invoice ? esc_attr($invoice->getClientId()) : ''; ?>">
740 774
741 - <!-- Client Information Display -->
742 775 <div id="client-info-display" class="mt-6 <?php echo $invoice && $invoice->getClientId() ? '' : 'hidden'; ?>">
743 776 <div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
744 777 <div class="flex items-center justify-between pb-4 border-b border-gray-200 mb-4">
745 778 <h4 class="text-lg font-semibold text-gray-800 flex items-center">
746 779 <i class="fas fa-user-check text-indigo-500 mr-3"></i>
747 - <span><?php _e('Client Information', 'easy-invoice'); ?></span>
780 + <span><?php esc_html_e('Client Information', 'easy-invoice'); ?></span>
748 781 </h4>
749 - <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() : ''))); ?>"
750 783 id="edit-selected-client"
751 - 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'; ?>"
752 785 target="_blank">
753 786 <i class="fas fa-pen mr-1"></i>
754 - <?php _e('Edit', 'easy-invoice'); ?>
787 + <?php esc_html_e('Edit', 'easy-invoice'); ?>
755 788 </a>
756 789 </div>
757 790
758 791 <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2">
759 - <!-- Client Name -->
760 792 <div class="flex items-start group">
761 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>
762 794 <div class="ml-3">
763 - <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>
764 796 <p id="display-client-name" class="text-sm font-semibold text-gray-800 mt-1">
765 797 <?php
766 798 if ($invoice && $invoice->getClientId()) {
767 799 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -777,13 +809,12 @@
777 809 </p>
778 810 </div>
779 811 </div>
780 812
781 - <!-- Company Name -->
782 813 <div class="flex items-start group">
783 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>
784 815 <div class="ml-3">
785 - <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>
786 817 <p id="display-client-company" class="text-sm font-semibold text-gray-800 mt-1">
787 818 <?php
788 819 if ($invoice && $invoice->getClientId()) {
789 820 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -799,13 +830,12 @@
799 830 </p>
800 831 </div>
801 832 </div>
802 833
803 - <!-- Email -->
804 834 <div class="flex items-start group">
805 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>
806 836 <div class="ml-3">
807 - <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>
808 838 <p id="display-client-email" class="text-sm text-gray-800 mt-1 break-all">
809 839 <?php
810 840 if ($invoice && $invoice->getClientId()) {
811 841 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -821,13 +851,12 @@
821 851 </p>
822 852 </div>
823 853 </div>
824 854
825 - <!-- Phone -->
826 855 <div class="flex items-start group">
827 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>
828 857 <div class="ml-3">
829 - <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>
830 859 <p id="display-client-phone" class="text-sm text-gray-800 mt-1">
831 860 <?php
832 861 if ($invoice && $invoice->getClientId()) {
833 862 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -843,13 +872,12 @@
843 872 </p>
844 873 </div>
845 874 </div>
846 875
847 - <!-- Website -->
848 876 <div class="flex items-start group md:col-span-2">
849 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>
850 878 <div class="ml-3">
851 - <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>
852 880 <p id="display-client-website" class="text-sm text-gray-800 mt-1">
853 881 <?php
854 882 if ($invoice && $invoice->getClientId()) {
855 883 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -865,13 +893,12 @@
865 893 </p>
866 894 </div>
867 895 </div>
868 896
869 - <!-- Address -->
870 897 <div class="flex items-start group md:col-span-2">
871 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>
872 899 <div class="ml-3">
873 - <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>
874 901 <p id="display-client-address" class="text-sm text-gray-800 mt-1 whitespace-pre-wrap">
875 902 <?php
876 903 if ($invoice && $invoice->getClientId()) {
877 904 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
@@ -890,17 +917,15 @@
890 917 </div>
891 918 </div>
892 919 </div>
893 920
894 - <!-- No Client Selected Message -->
895 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' : ''; ?>">
896 - <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>
897 923 </div>
898 924 </div>
899 925 <?php elseif ($tab['id'] === 'discounts-taxes-tab'): ?>
900 - <!-- Discount & Tax Tab Content -->
901 926 <div>
902 - <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>
903 928 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
904 929 <?php
905 930 // Get all discount and tax fields
906 931 $discount_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'discount');
@@ -913,34 +938,32 @@
913 938 return ($a['order'] ?? 0) - ($b['order'] ?? 0);
914 939 });
915 940
916 941 foreach ($all_fields as $field):
917 - 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.
918 943 endforeach;
919 944 ?>
920 945 </div>
921 946 </div>
922 947 <?php elseif ($tab['id'] === 'payments-tab'): ?>
923 - <!-- Payment Tab Content -->
924 948 <div>
925 - <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>
926 950 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
927 951 <?php
928 952 $payment_fields = $form_manager->getFieldsBySection('payments-tab', 'payment');
929 953 foreach ($payment_fields as $field):
930 - 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.
931 955 endforeach;
932 956 ?>
933 957 </div>
934 958
935 - <!-- Currency Section -->
936 959 <div class="mt-8 border-t border-gray-200 pt-6">
937 - <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>
938 961 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
939 962 <?php
940 963 $currency_fields = $form_manager->getFieldsBySection('payments-tab', 'currency');
941 964 foreach ($currency_fields as $field):
942 - 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.
943 966 endforeach;
944 967 ?>
945 968 </div>
946 969 </div>
@@ -950,15 +973,14 @@
950 973 <?php endforeach; ?>
951 974 </form>
952 975 </div>
953 976
954 - <!-- Template Gallery Modal -->
955 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">
956 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">
957 979 <div class="flex justify-between items-center mb-4">
958 - <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>
959 981 <button type="button" class="close-gallery-modal text-gray-400 hover:text-gray-500 focus:outline-none">
960 - <span class="sr-only"><?php _e('Close', 'easy-invoice'); ?></span>
982 + <span class="sr-only"><?php esc_html_e('Close', 'easy-invoice'); ?></span>
961 983 <i class="fas fa-times text-xl"></i>
962 984 </button>
963 985 </div>
964 986
@@ -968,9 +990,9 @@
968 990 foreach ($all_templates as $template_id => $template) :
969 991 $is_current = ($current_template === $template_id);
970 992 $selected_class = $is_current ? 'selected' : '';
971 993 ?>
972 - <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'; ?>">
973 995 <div class="p-4 border rounded-lg">
974 996 <div class="flex items-center mb-2">
975 997 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
976 998 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
@@ -986,18 +1008,17 @@
986 1008 </div>
987 1009
988 1010 <div class="flex justify-end mt-4 space-x-3">
989 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">
990 - <?php _e('Apply', 'easy-invoice'); ?>
1012 + <?php esc_html_e('Apply', 'easy-invoice'); ?>
991 1013 </button>
992 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">
993 - <?php _e('Close', 'easy-invoice'); ?>
1015 + <?php esc_html_e('Close', 'easy-invoice'); ?>
994 1016 </button>
995 1017 </div>
996 1018 </div>
997 1019 </div>
998 1020
999 - <!-- Hidden Template for Invoice Items -->
1000 1021 <template id="invoice-item-template" style="display: none;">
1001 1022 <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;">
1002 1023 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
1003 1024 <div class="flex items-center">
@@ -1005,9 +1026,9 @@
1005 1026 <i class="fas fa-chevron-down text-sm"></i>
1006 1027 </button>
1007 1028 <h3 class="text-base font-normal text-gray-800 flex-1">
1008 1029 <?php
1009 - echo !empty($title) ? esc_html($title) : __('New Item', 'easy-invoice');
1030 + echo !empty($title) ? esc_html($title) : esc_html__('New Item', 'easy-invoice');
1010 1031 ?>
1011 1032 </h3>
1012 1033 </div>
1013 1034 <div class="flex items-center gap-3">
@@ -1014,9 +1035,9 @@
1014 1035 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
1015 1036 <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span>
1016 1037 = $<span class="total-summary">0.00</span>
1017 1038 </div>
1018 - <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')); ?>">
1019 1040 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1020 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"/>
1021 1042 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
1022 1043 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
@@ -1022,15 +1043,15 @@
1022 1043 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
1023 1044 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
1024 1045 </svg>
1025 1046 </button>
1026 - <?php if (class_exists('EasyInvoicePro\\Services\\ItemLibraryService')): ?>
1027 - <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'); ?>">
1047 + <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
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')); ?>">
1028 1049 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1029 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"/>
1030 1051 </svg>
1031 1052 </button>
1032 - <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')); ?>">
1033 1054 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1034 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"/>
1035 1056 </svg>
1036 1057 </button>
@@ -1035,37 +1056,32 @@
1035 1056 </svg>
1036 1057 </button>
1037 1058 <?php endif; ?>
1038 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">
1039 - <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'); ?>
1040 1061 </button>
1041 1062 </div>
1042 1063 </div>
1043 1064 <div class="item-content">
1044 - <?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. ?>
1045 1066 </div>
1046 1067 </div>
1047 1068 </template>
1048 1069
1049 - <!-- Add Client Modal -->
1050 - <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">
1051 - <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">
1052 - <div class="flex justify-between items-center mb-4">
1053 - <h3 class="text-lg font-medium text-gray-900">Add New Client</h3>
1054 - <button type="button" class="close-modal text-gray-400 hover:text-gray-500">
1055 - <span class="sr-only">Close</span>
1056 - <i class="fas fa-times"></i>
1057 - </button>
1058 - </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 + ?>
1059 1083
1060 - <?php
1061 - // Unset the $client variable from the foreach loop to ensure clean add form
1062 - unset($client);
1063 - include_once EASY_INVOICE_PLUGIN_DIR . 'templates/client-form.php';
1064 - ?>
1065 - </div>
1066 - </div>
1067 -
1068 1084 <?php
1069 1085
1070 1086 // Always localize script with AJAX URL and nonce
1071 1087 wp_localize_script('easy-invoice-form', 'easyInvoice', array(
@@ -1258,22 +1274,22 @@
1258 1274 $('#client-search-loading').hide();
1259 1275
1260 1276 if (response.success && response.data.length > 0) {
1261 1277 response.data.forEach(function(client) {
1262 - const option = $(`
1263 - <div class="client-option px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0"
1264 - data-client-id="${client.id}"
1265 - data-client-name="${client.name.replace(/"/g, '&quot;')}"
1266 - data-client-email="${client.email.replace(/"/g, '&quot;')}"
1267 - data-client-company="${(client.company || '').replace(/"/g, '&quot;')}"
1268 - data-client-phone="${(client.phone || '').replace(/"/g, '&quot;')}"
1269 - data-client-website="${(client.website || '').replace(/"/g, '&quot;')}"
1270 - data-client-address="${(client.address || '').replace(/"/g, '&quot;')}"
1271 - onclick="selectClientFromOption(this)">
1272 - <div class="font-medium text-gray-900">${client.name}</div>
1273 - <div class="text-sm text-gray-600">${client.email}</div>
1274 - </div>
1275 - `);
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 || ''));
1276 1292
1277 1293 $('#client-search-list').append(option);
1278 1294 });
1279 1295 $('#client-search-results').show();
@@ -1301,9 +1317,10 @@
1301 1317 $('#client-info-display').show().removeClass('hidden');
1302 1318 $('#no-client-message').hide().removeClass('hidden');
1303 1319
1304 1320 // Update the edit client button URL
1305 - $('#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);
1306 1323
1307 1324 // If we have full client data, populate all fields directly
1308 1325 if (clientData) {
1309 1326 $('#display-client-name').text(clientData.name || clientName || '-');
@@ -1365,18 +1382,18 @@
1365 1382 <script>
1366 1383 // Invoice field configuration and pre-loaded data
1367 1384 window.easyInvoice = window.easyInvoice || {};
1368 1385 <?php
1369 -$json_field_config = json_encode($invoice_field_config);
1386 +$json_field_config = wp_json_encode($invoice_field_config);
1370 1387 ?>
1371 -window.easyInvoice.fieldConfig = <?php echo $json_field_config; ?>;
1372 -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()); ?>';
1373 1390 window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>;
1374 1391 window.easyInvoice.translations = {
1375 - premiumTemplates: '<?php _e('Premium Templates', 'easy-invoice'); ?>',
1376 - premiumTemplatesDescription: '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.', 'easy-invoice'); ?>',
1377 - premiumFeatureMessage: '<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>',
1378 - 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')); ?>'
1379 1396 };
1380 1397 <?php if ($client_data): ?>
1381 1398 window.easyInvoice.clientData = <?php echo json_encode($client_data); ?>;
1382 1399 <?php endif; ?>