| @@ -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, '"')}" | |
| 1266 | - data-client-email="${client.email.replace(/"/g, '"')}" | |
| 1267 | - data-client-company="${(client.company || '').replace(/"/g, '"')}" | |
| 1268 | - data-client-phone="${(client.phone || '').replace(/"/g, '"')}" | |
| 1269 | - data-client-website="${(client.website || '').replace(/"/g, '"')}" | |
| 1270 | - data-client-address="${(client.address || '').replace(/"/g, '"')}" | |
| 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; ?> |