PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.4
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.4
2.4.3 2.4.4 2.4.2 2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 All 60 releases
← All changes | templates/payments/list.php +147 -119 2.3.3 → 2.4.4 View file →
@@ -37,63 +37,26 @@
37 37 'pending_payments' => 0,
38 38 'failed_payments' => 0
39 39 ], $stats);
40 40
41 -// Initialize amounts_by_currency array
42 -$amounts_by_currency = [];
43 -// Calculate stats from the payments array
44 -foreach ($payments as $payment) {
45 - $amount = floatval($payment->getAmount());
46 - $status = $payment->getStatus();
47 -
48 - $stats['total_amount'] += $amount;
49 -
50 - switch ($status) {
51 - case 'completed':
52 - $stats['completed_payments']++;
53 - break;
54 - case 'pending':
55 - $stats['pending_payments']++;
56 - break;
57 - case 'failed':
58 - $stats['failed_payments']++;
59 - break;
60 - }
61 -
62 - // Calculate currency breakdown
63 - $payment_currency = $payment->getCurrency();
64 -
65 - // If currency is empty or "global", get the actual currency that was used
66 - if (empty($payment_currency) || $payment_currency === 'global') {
67 - $actual_currency = get_post_meta($payment->getId(), '_currency', true);
68 - $payment_currency = !empty($actual_currency) ? $actual_currency : get_option('easy_invoice_currency_code', 'USD');
69 - }
70 -
71 - // If currency is still "global", use the global setting
72 - if ($payment_currency === 'global') {
73 - $payment_currency = get_option('easy_invoice_currency_code', 'USD');
41 +// Site-wide figures come from the controller (one query each); the page's own
42 +// twenty rows used to be added on top of them, which inflated "Completed" and
43 +// "Awaiting confirmation" and made "Total amount" the page's sum.
44 +$amounts_by_currency = $amounts_by_currency ?? [];
45 +if (empty($amounts_by_currency) && !empty($stats['total_amount'])) {
46 + $ei_code = strtoupper((string) get_option('easy_invoice_currency_code', 'USD'));
47 + $amounts_by_currency[$ei_code] = [
48 + 'amount' => (float) $stats['total_amount'],
49 + 'symbol' => \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($ei_code),
50 + ];
74 51 }
75 52
76 - $payment_currency_symbol = $payment->getCurrencySymbol() ?: \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($payment_currency);
77 -
78 - // Normalize currency code to uppercase for consistent grouping
79 - $payment_currency = strtoupper($payment_currency);
80 -
81 - // Group amounts by currency
82 - if (!isset($amounts_by_currency[$payment_currency])) {
83 - $amounts_by_currency[$payment_currency] = [
84 - 'amount' => 0,
85 - 'symbol' => $payment_currency_symbol
86 - ];
87 -}
88 - $amounts_by_currency[$payment_currency]['amount'] += $amount;
89 -}
90 -
91 53 // Check for bulk action notifications
92 54 $notification = false;
93 55
94 56 if (isset($_GET['bulk_trashed']) && $_GET['bulk_trashed'] > 0) {
95 57 $count = intval($_GET['bulk_trashed']);
58 + /* translators: %s: number of items. */
96 59 easy_invoice_display_notification('success', sprintf(_n('%s payment moved to trash.', '%s payments moved to trash.', $count, 'easy-invoice'), $count));
97 60 $notification = true;
98 61 }
99 62
@@ -98,8 +61,9 @@
98 61 }
99 62
100 63 if (isset($_GET['bulk_restored']) && $_GET['bulk_restored'] > 0) {
101 64 $count = intval($_GET['bulk_restored']);
65 + /* translators: %s: number of items. */
102 66 easy_invoice_display_notification('success', sprintf(_n('%s payment restored from trash.', '%s payments restored from trash.', $count, 'easy-invoice'), $count));
103 67 $notification = true;
104 68 }
105 69
@@ -104,8 +68,9 @@
104 68 }
105 69
106 70 if (isset($_GET['bulk_deleted']) && $_GET['bulk_deleted'] > 0) {
107 71 $count = intval($_GET['bulk_deleted']);
72 + /* translators: %s: number of items. */
108 73 easy_invoice_display_notification('success', sprintf(_n('%s payment permanently deleted.', '%s payments permanently deleted.', $count, 'easy-invoice'), $count));
109 74 $notification = true;
110 75 }
111 76
@@ -127,10 +92,10 @@
127 92 <div class="p-8">
128 93 <div class="ei-app-page-header bg-white border-b border-gray-200 px-6" style="margin-left: -2rem; margin-top: -2rem; margin-right: -2rem; padding-left: 2rem; padding-right: 2rem;">
129 94 <div class="flex items-center justify-between">
130 95 <div>
131 - <h1 class="text-2xl font-bold text-gray-900"><?php _e('Payments', 'easy-invoice'); ?></h1>
132 - <p class="mt-1 text-sm text-gray-500"><?php _e('Manage all payment records', 'easy-invoice'); ?></p>
96 + <h1 class="text-2xl font-bold text-gray-900"><?php esc_html_e('Payments', 'easy-invoice'); ?></h1>
97 + <p class="mt-1 text-sm text-gray-500"><?php esc_html_e('Manage all payment records', 'easy-invoice'); ?></p>
133 98 </div>
134 99 <div class="flex items-center space-x-3">
135 100 <?php
136 101 // "Export All" is part of the bulk_operations addon
@@ -140,14 +105,14 @@
140 105 ?>
141 106 <?php if ($ei_bulk_ops_loaded): ?>
142 107 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;">
143 108 <input type="hidden" name="action" value="easy_invoice_export_all_payments" />
144 - <input type="hidden" name="_wpnonce" value="<?php echo wp_create_nonce('easy_invoice_export_all_payments'); ?>" />
109 + <input type="hidden" name="_wpnonce" value="<?php echo esc_attr(wp_create_nonce('easy_invoice_export_all_payments')); ?>" />
145 110 <button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
146 111 <svg class="w-4 h-4 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
147 112 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
148 113 </svg>
149 - <?php _e('Export All', 'easy-invoice'); ?>
114 + <?php esc_html_e('Export All', 'easy-invoice'); ?>
150 115 </button>
151 116 </form>
152 117 <?php elseif (!easy_invoice_has_pro()): ?>
153 118 <button type="button" id="export-all-payments-btn" class="premium-export-btn inline-flex items-center px-4 py-2 border border-gray-300 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 transition-colors duration-200 relative">
@@ -153,9 +118,9 @@
153 118 <button type="button" id="export-all-payments-btn" class="premium-export-btn inline-flex items-center px-4 py-2 border border-gray-300 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 transition-colors duration-200 relative">
154 119 <svg class="w-4 h-4 mr-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
155 120 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
156 121 </svg>
157 - <?php _e('Export All', 'easy-invoice'); ?>
122 + <?php esc_html_e('Export All', 'easy-invoice'); ?>
158 123 <svg class="ml-2 w-5 h-5 text-purple-600 crown-icon" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
159 124 <path d="M12 8L15 13.2L18 10.5L17.3 14H6.7L6 10.5L9 13.2L12 8M12 4L8.5 10L3 5L5 16H19L21 5L15.5 10L12 4Z"/>
160 125 </svg>
161 126 </button>
@@ -172,14 +137,16 @@
172 137 });
173 138 </script>
174 139 <?php endif; ?>
175 140
176 - <a href="?page=easy-invoice-payment-new" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
141 + <?php if (easy_invoice_user_can('ei_record_payment')) : ?>
142 +<a href="?page=easy-invoice-payment-new" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
177 143 <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
178 144 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
179 145 </svg>
180 - <?php _e('Add New Payment', 'easy-invoice'); ?>
146 + <?php esc_html_e('Add New Payment', 'easy-invoice'); ?>
181 147 </a>
148 +<?php endif; ?>
182 149 </div>
183 150 </div>
184 151 </div>
185 152
@@ -191,10 +158,10 @@
191 158 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
192 159 </svg>
193 160 </div>
194 161 <div>
195 - <h3 class="text-sm font-medium text-gray-500">Total Payments</h3>
196 - <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo $stats['total_payments']; ?></p>
162 + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Payments', 'easy-invoice'); ?></h3>
163 + <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($stats['total_payments']); ?></p>
197 164 </div>
198 165 </div>
199 166 </div>
200 167 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
@@ -204,20 +171,20 @@
204 171 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
205 172 </svg>
206 173 </div>
207 174 <div>
208 - <h3 class="text-sm font-medium text-gray-500">Total Amount</h3>
175 + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Amount', 'easy-invoice'); ?></h3>
209 176 <?php if (!empty($amounts_by_currency)): ?>
210 177 <div class="mt-1">
211 178 <?php foreach ($amounts_by_currency as $currency => $data): ?>
212 179 <p class="text-lg font-bold text-gray-900">
213 - <?php echo $data['symbol'] . number_format($data['amount'], 2); ?>
214 - <span class="text-sm font-normal text-gray-500"><?php echo strtoupper($currency ?? 'USD'); ?></span>
180 + <?php echo esc_html($data['symbol'] . number_format($data['amount'], 2)); ?>
181 + <span class="text-sm font-normal text-gray-500"><?php echo esc_html(strtoupper($currency ?? 'USD')); ?></span>
215 182 </p>
216 183 <?php endforeach; ?>
217 184 </div>
218 185 <?php else: ?>
219 - <p class="mt-1 text-lg font-bold text-gray-900"><?php echo get_option('easy_invoice_currency_symbol', '$') . '0.00'; ?></p>
186 + <p class="mt-1 text-lg font-bold text-gray-900"><?php echo esc_html(get_option('easy_invoice_currency_symbol', '$') . '0.00'); ?></p>
220 187 <?php endif; ?>
221 188 </div>
222 189 </div>
223 190 </div>
@@ -228,10 +195,10 @@
228 195 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
229 196 </svg>
230 197 </div>
231 198 <div>
232 - <h3 class="text-sm font-medium text-gray-500">Completed Payments</h3>
233 - <p class="mt-1 text-2xl font-bold text-green-600"><?php echo $stats['completed_payments']; ?></p>
199 + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Completed Payments', 'easy-invoice'); ?></h3>
200 + <p class="mt-1 text-2xl font-bold text-green-600"><?php echo esc_html($stats['completed_payments']); ?></p>
234 201 </div>
235 202 </div>
236 203 </div>
237 204 <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1">
@@ -241,10 +208,10 @@
241 208 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
242 209 </svg>
243 210 </div>
244 211 <div>
245 - <h3 class="text-sm font-medium text-gray-500">Pending Payments</h3>
246 - <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo $stats['pending_payments']; ?></p>
212 + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Pending Payments', 'easy-invoice'); ?></h3>
213 + <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo esc_html($stats['pending_payments']); ?></p>
247 214 </div>
248 215 </div>
249 216 </div>
250 217 </div>
@@ -306,9 +273,9 @@
306 273 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
307 274 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
308 275 }
309 276
310 - echo $link;
277 + echo wp_kses_post($link);
311 278 }
312 279 echo '</div>';
313 280 }
314 281 ?>
@@ -334,11 +301,11 @@
334 301 $class = $is_current
335 302 ? 'border-b-2 border-indigo-500 text-indigo-600 bg-white'
336 303 : 'border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300';
337 304 ?>
338 - <a href="?page=easy-invoice-payments&view=<?php echo $view . $status_param; ?>"
339 - class="flex items-center whitespace-nowrap py-4 px-6 font-medium text-sm transition-colors duration-200 <?php echo $class; ?>">
340 - <?php echo $label; ?>
305 + <a href="?page=easy-invoice-payments&view=<?php echo esc_attr($view . $status_param); ?>"
306 + class="flex items-center whitespace-nowrap py-4 px-6 font-medium text-sm transition-colors duration-200 <?php echo esc_attr($class); ?>">
307 + <?php echo esc_html($label); ?>
341 308 </a>
342 309 <?php } ?>
343 310 </nav>
344 311 </div>
@@ -351,16 +318,16 @@
351 318 <form id="bulk-action-form" method="post" class="flex items-center gap-2">
352 319 <?php wp_nonce_field('easy_invoice_payment_bulk_action', 'easy_invoice_payment_bulk_nonce'); ?>
353 320 <input type="hidden" name="action" value="easy_invoice_payment_bulk_action">
354 321
355 - <select name="bulk_action" class="block w-full px-3 py-2 border border-gray-300 rounded-md leading-5 bg-white text-gray-700 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
356 - <option value="">Bulk Actions</option>
322 + <select name="bulk_action" aria-label="Bulk action for selected payments" class="block w-full px-3 py-2 border border-gray-300 rounded-md leading-5 bg-white text-gray-700 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
323 + <option value=""><?php esc_html_e('Bulk Actions', 'easy-invoice'); ?></option>
357 324 <?php if ($current_view === 'trash'): ?>
358 - <option value="restore">Restore</option>
359 - <option value="delete">Delete Permanently</option>
325 + <option value="restore"><?php esc_html_e('Restore', 'easy-invoice'); ?></option>
326 + <option value="delete"><?php esc_html_e('Delete Permanently', 'easy-invoice'); ?></option>
360 327 <?php else: ?>
361 - <option value="trash">Move to Trash</option>
362 - <option value="delete">Delete Permanently</option>
328 + <option value="trash"><?php esc_html_e('Move to Trash', 'easy-invoice'); ?></option>
329 + <option value="delete"><?php esc_html_e('Delete Permanently', 'easy-invoice'); ?></option>
363 330 <?php endif; ?>
364 331 </select>
365 332
366 333 <button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
@@ -378,13 +345,13 @@
378 345 <table class="min-w-full divide-y divide-gray-200 ei-payments-table">
379 346 <thead class="bg-gray-50">
380 347 <tr>
381 348 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
382 - <input type="checkbox" id="select-all-payments" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
349 + <input type="checkbox" id="select-all-payments" aria-label="Select all payments on this page" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
383 350 </th>
384 351 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="id">
385 352 <div class="flex items-center space-x-1">
386 - <span>Payment ID</span>
353 + <span><?php esc_html_e('Payment ID', 'easy-invoice'); ?></span>
387 354 <div class="sort-indicator">
388 355 <i class="fas fa-sort text-gray-400"></i>
389 356 </div>
390 357 </div>
@@ -390,9 +357,9 @@
390 357 </div>
391 358 </th>
392 359 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="invoice">
393 360 <div class="flex items-center space-x-1">
394 - <span>Invoice</span>
361 + <span><?php esc_html_e('Invoice', 'easy-invoice'); ?></span>
395 362 <div class="sort-indicator">
396 363 <i class="fas fa-sort text-gray-400"></i>
397 364 </div>
398 365 </div>
@@ -398,9 +365,9 @@
398 365 </div>
399 366 </th>
400 367 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="amount">
401 368 <div class="flex items-center space-x-1">
402 - <span>Amount</span>
369 + <span><?php esc_html_e('Amount', 'easy-invoice'); ?></span>
403 370 <div class="sort-indicator">
404 371 <i class="fas fa-sort text-gray-400"></i>
405 372 </div>
406 373 </div>
@@ -406,9 +373,9 @@
406 373 </div>
407 374 </th>
408 375 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="method">
409 376 <div class="flex items-center space-x-1">
410 - <span>Method</span>
377 + <span><?php esc_html_e('Method', 'easy-invoice'); ?></span>
411 378 <div class="sort-indicator">
412 379 <i class="fas fa-sort text-gray-400"></i>
413 380 </div>
414 381 </div>
@@ -414,9 +381,9 @@
414 381 </div>
415 382 </th>
416 383 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="status">
417 384 <div class="flex items-center space-x-1">
418 - <span>Status</span>
385 + <span><?php esc_html_e('Status', 'easy-invoice'); ?></span>
419 386 <div class="sort-indicator">
420 387 <i class="fas fa-sort text-gray-400"></i>
421 388 </div>
422 389 </div>
@@ -422,9 +389,9 @@
422 389 </div>
423 390 </th>
424 391 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="date">
425 392 <div class="flex items-center space-x-1">
426 - <span>Date</span>
393 + <span><?php esc_html_e('Date', 'easy-invoice'); ?></span>
427 394 <div class="sort-indicator">
428 395 <i class="fas fa-sort text-gray-400"></i>
429 396 </div>
430 397 </div>
@@ -430,9 +397,9 @@
430 397 </div>
431 398 </th>
432 399 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 hidden sm:table-cell" data-sort="type">
433 400 <div class="flex items-center space-x-1">
434 - <span>Type</span>
401 + <span><?php esc_html_e('Type', 'easy-invoice'); ?></span>
435 402 <div class="sort-indicator">
436 403 <i class="fas fa-sort text-gray-400"></i>
437 404 </div>
438 405 </div>
@@ -454,12 +421,12 @@
454 421 // Temporarily show all payments regardless of invoice
455 422 ?>
456 423 <tr>
457 424 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
458 - <input type="checkbox" name="payment_ids[]" value="<?php echo $payment->getId(); ?>" form="bulk-action-form" class="payment-checkbox rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
425 + <input type="checkbox" name="payment_ids[]" value="<?php echo (int) $payment->getId(); ?>" form="bulk-action-form" class="payment-checkbox rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
459 426 </td>
460 427 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
461 - #<?php echo $payment->getId(); ?>
428 + #<?php echo (int) $payment->getId(); ?>
462 429 </td>
463 430 <td class="px-6 py-4 text-sm text-gray-500">
464 431 <?php if ($invoice_id): ?>
465 432 <?php
@@ -467,10 +434,10 @@
467 434 if ($invoice_post && $invoice_post->post_type === 'easy_invoice'):
468 435 $invoice = new \EasyInvoice\Models\Invoice($invoice_id);
469 436 ?>
470 437 <div>
471 - <a href="<?php echo get_permalink($invoice->getId()); ?>" class="text-indigo-600 hover:text-indigo-900 font-medium" target="_blank">
472 - <?php echo $invoice->getNumber(); ?>
438 + <a href="<?php echo esc_url(get_permalink($invoice->getId())); ?>" class="text-indigo-600 hover:text-indigo-900 font-medium" target="_blank">
439 + <?php echo esc_html($invoice->getNumber()); ?>
473 440 </a>
474 441 <div class="text-xs text-gray-400 mt-1">
475 442 <?php echo esc_html($invoice->getTitle() ?: __('Untitled Invoice', 'easy-invoice')); ?>
476 443 </div>
@@ -476,22 +443,22 @@
476 443 </div>
477 444 </div>
478 445 <?php else: ?>
479 446 <span class="text-gray-400">
480 - <?php _e('Invoice not found', 'easy-invoice'); ?>
447 + <?php esc_html_e('Invoice not found', 'easy-invoice'); ?>
481 448 <?php if (current_user_can('manage_options')): ?>
482 - <br><small class="text-xs text-gray-500">ID: <?php echo $invoice_id; ?> (Type: <?php echo $invoice_post ? $invoice_post->post_type : 'null'; ?>)</small>
449 + <br><small class="text-xs text-gray-500">ID: <?php echo (int) $invoice_id; ?> (Type: <?php echo esc_html($invoice_post ? $invoice_post->post_type : 'null'); ?>)</small>
483 450 <?php endif; ?>
484 451 </span>
485 452 <?php endif; ?>
486 453 <?php else: ?>
487 454 <span class="text-gray-400">
488 - <?php _e('No invoice ID', 'easy-invoice'); ?>
455 + <?php esc_html_e('No invoice ID', 'easy-invoice'); ?>
489 456 </span>
490 457 <?php endif; ?>
491 458 </td>
492 459 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
493 - <?php echo $payment->getCurrencySymbol() . number_format($payment->getAmount(), 2); ?>
460 + <?php echo esc_html($payment->getCurrencySymbol() . number_format((float) $payment->getAmount(), 2)); ?>
494 461 </td>
495 462 <td class="px-6 py-4 text-sm text-gray-500">
496 463 <?php
497 464 // Get payment method label dynamically from registered gateways
@@ -514,8 +481,16 @@
514 481 $method_label = __('Manual', 'easy-invoice');
515 482 }
516 483
517 484 echo esc_html($method_label);
485 + $ei_reference = (string) get_post_meta((int) $payment->getId(), \EasyInvoice\Services\OfflinePayments::META_REFERENCE, true);
486 + if ('' !== $ei_reference) :
487 + /* translators: %s: reference the client gave. */
488 + echo '<span class="block text-xs text-gray-400" title="' . esc_attr__('Reference given by the client', 'easy-invoice') . '">' . esc_html(sprintf(__('Ref: %s', 'easy-invoice'), $ei_reference)) . '</span>';
489 + endif;
490 + if (\EasyInvoice\Services\OfflinePayments::hasProof((int) $payment->getId())) :
491 + echo '<a class="inline-flex items-center gap-1 mt-1 text-xs font-medium text-indigo-600 hover:text-indigo-900" target="_blank" rel="noopener" href="' . esc_url(\EasyInvoice\Services\OfflinePayments::proofUrl((int) $payment->getId())) . '"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path></svg>' . esc_html__('View receipt', 'easy-invoice') . '</a>';
492 + endif;
518 493 ?>
519 494 </td>
520 495 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
521 496 <?php
@@ -528,11 +503,18 @@
528 503 $status_class = 'bg-green-100 text-green-800';
529 504 $status_text = __('Completed', 'easy-invoice');
530 505 break;
531 506 case 'pending':
507 + case 'pending-bank':
508 + case 'pending-cheque':
509 + case 'pending_verification':
532 510 $status_class = 'bg-yellow-100 text-yellow-800';
533 - $status_text = __('Pending', 'easy-invoice');
511 + $status_text = __('Awaiting confirmation', 'easy-invoice');
534 512 break;
513 + case 'rejected':
514 + $status_class = 'bg-gray-200 text-gray-700';
515 + $status_text = __('Rejected', 'easy-invoice');
516 + break;
535 517 case 'failed':
536 518 $status_class = 'bg-red-100 text-red-800';
537 519 $status_text = __('Failed', 'easy-invoice');
538 520 break;
@@ -540,40 +522,49 @@
540 522 $status_class = 'bg-gray-100 text-gray-800';
541 523 $status_text = ucfirst($status);
542 524 }
543 525 ?>
544 - <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <?php echo $status_class; ?>">
545 - <?php echo $status_text; ?>
526 + <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <?php echo esc_attr($status_class); ?>">
527 + <?php echo esc_html($status_text); ?>
546 528 </span>
547 529 </td>
548 530 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
549 - <?php echo $payment->getPaymentDate() ? date('M j, Y', strtotime($payment->getPaymentDate())) : date('M j, Y', strtotime($payment->getPost()->post_date)); ?>
531 + <?php echo esc_html($payment->getPaymentDate() ? gmdate('M j, Y', strtotime($payment->getPaymentDate())) : gmdate('M j, Y', strtotime((string) get_post_field('post_date', $payment->getId())) ?: time())); ?>
550 532 </td>
551 533 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 hidden sm:table-cell">
552 - <?php echo ucfirst($payment->getPaymentType() ?: 'one-time'); ?>
534 + <?php echo esc_html(ucfirst($payment->getPaymentType() ?: 'one-time')); ?>
553 535 </td>
554 536 <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
555 537 <div class="flex items-center space-x-2">
556 - <a href="?page=easy-invoice-payments&action=view&id=<?php echo $payment->getId(); ?>" class="text-indigo-600 hover:text-indigo-900">
538 + <a href="?page=easy-invoice-payments&action=view&id=<?php echo (int) $payment->getId(); ?>" class="text-indigo-600 hover:text-indigo-900">
557 539 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
558 540 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
559 541 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
560 542 </svg>
561 543 </a>
562 - <a href="?page=easy-invoice-payments&action=edit&id=<?php echo $payment->getId(); ?>" class="text-blue-600 hover:text-blue-900">
544 + <a href="?page=easy-invoice-payments&action=edit&id=<?php echo (int) $payment->getId(); ?>" class="text-blue-600 hover:text-blue-900">
563 545 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
564 546 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
565 547 </svg>
566 548 </a>
567 - <?php if (in_array($status, ['pending', 'pending-bank', 'pending-cheque'])): ?>
568 - <button type="button" class="approve-payment-btn text-green-600 hover:text-green-900"
569 - data-payment-id="<?php echo $payment->getId(); ?>"
570 - data-invoice-id="<?php echo $payment->getInvoiceId(); ?>"
571 - data-nonce="<?php echo wp_create_nonce('easy_invoice_approve_payment'); ?>">
549 + <?php if (in_array($status, \EasyInvoice\Services\OfflinePayments::pendingStatuses(), true)): ?>
550 + <button type="button" class="approve-payment-btn text-green-600 hover:text-green-900" title="<?php esc_attr_e('Confirm this payment', 'easy-invoice'); ?>"
551 + data-payment-id="<?php echo (int) $payment->getId(); ?>"
552 + data-invoice-id="<?php echo esc_attr($payment->getInvoiceId()); ?>"
553 + data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_approve_payment')); ?>">
572 554 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
573 555 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
574 556 </svg>
557 + <span class="sr-only"><?php esc_html_e('Confirm', 'easy-invoice'); ?></span>
575 558 </button>
559 + <button type="button" class="reject-payment-btn text-red-600 hover:text-red-900" title="<?php esc_attr_e('Reject this payment', 'easy-invoice'); ?>"
560 + data-payment-id="<?php echo (int) $payment->getId(); ?>"
561 + data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_approve_payment')); ?>">
562 + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
563 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
564 + </svg>
565 + <span class="sr-only"><?php esc_html_e('Reject', 'easy-invoice'); ?></span>
566 + </button>
576 567 <?php endif; ?>
577 568
578 569 <?php
579 570 // Hook for additional payment actions (used by Pro plugin for receipts)
@@ -584,10 +575,25 @@
584 575 </tr>
585 576 <?php endforeach; ?>
586 577 <?php else : ?>
587 578 <tr>
588 - <td colspan="9" class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-center">
589 - <?php _e('No payments found.', 'easy-invoice'); ?>
579 + <?php
580 + // Was a bare "No payments found." with no way forward. Every
581 + // other empty state in the plugin offers the next action, so
582 + // this one now points at Add New Payment too.
583 + //
584 + // `whitespace-nowrap` also had to go: it is meant for table
585 + // cells holding short values, and on a 9-column empty row it
586 + // stopped the message wrapping.
587 + $ei_add_payment_link = '<a href="' . esc_url(admin_url('admin.php?page=easy-invoice-payment-new')) . '" class="text-indigo-600 hover:text-indigo-900">'
588 + . esc_html__('Record your first payment', 'easy-invoice')
589 + . '</a>';
590 + ?>
591 + <td colspan="9" class="px-6 py-4 text-sm text-gray-500 text-center">
592 + <?php
593 + /* translators: %s: link reading "Record your first payment". */
594 + printf(esc_html__('No payments found. %s.', 'easy-invoice'), wp_kses_post($ei_add_payment_link));
595 + ?>
590 596 </td>
591 597 </tr>
592 598 <?php endif; ?>
593 599 </tbody>
@@ -652,9 +658,9 @@
652 658 if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) {
653 659 $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link);
654 660 }
655 661
656 - echo $link;
662 + echo wp_kses_post($link);
657 663 }
658 664 echo '</div>';
659 665 }
660 666 ?>
@@ -678,19 +684,19 @@
678 684 </svg>
679 685 </div>
680 686 <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
681 687 <h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
682 - <?php _e('Approve Payment', 'easy-invoice'); ?>
688 + <?php esc_html_e('Approve Payment', 'easy-invoice'); ?>
683 689 </h3>
684 690 <div class="mt-2">
685 691 <p class="text-sm text-gray-500">
686 - <?php _e('You are about to approve this payment. This will mark the invoice as paid and update the payment records.', 'easy-invoice'); ?>
692 + <span data-ei-mode-text="approve"><?php esc_html_e('Confirm that this payment arrived. The payment is marked completed and the invoice becomes Paid when its balance is covered, or Partially paid otherwise.', 'easy-invoice'); ?></span><span data-ei-mode-text="reject" hidden><?php esc_html_e('Reject this submission. The record is kept for the audit trail, nothing counts toward the balance, and the invoice keeps its status.', 'easy-invoice'); ?></span>
687 693 </p>
688 694 <div class="mt-4">
689 695 <label for="approval-notes" class="block text-sm font-medium text-gray-700">
690 - <?php _e('Notes (optional)', 'easy-invoice'); ?>
696 + <?php esc_html_e('Notes (optional)', 'easy-invoice'); ?>
691 697 </label>
692 - <textarea id="approval-notes" name="notes" rows="3" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="<?php _e('Add any notes about this payment approval...', 'easy-invoice'); ?>"></textarea>
698 + <textarea id="approval-notes" name="notes" rows="3" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="<?php echo esc_js(__('Add any notes about this payment approval...', 'easy-invoice')); ?>"></textarea>
693 699 </div>
694 700 </div>
695 701 </div>
696 702 </div>
@@ -696,12 +702,12 @@
696 702 </div>
697 703 </div>
698 704 <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
699 705 <button type="button" id="confirm-approve-btn" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-600 text-base font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm">
700 - <?php _e('Approve Payment', 'easy-invoice'); ?>
706 + <span data-ei-mode-text="approve"><?php esc_html_e('Confirm payment', 'easy-invoice'); ?></span><span data-ei-mode-text="reject" hidden><?php esc_html_e('Reject payment', 'easy-invoice'); ?></span>
701 707 </button>
702 708 <button type="button" id="cancel-approve-btn" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
703 - <?php _e('Cancel', 'easy-invoice'); ?>
709 + <?php esc_html_e('Cancel', 'easy-invoice'); ?>
704 710 </button>
705 711 </div>
706 712 </div>
707 713 </div>
@@ -751,9 +757,9 @@
751 757 </style>
752 758
753 759 <script>
754 760 // Define ajaxurl for AJAX requests
755 -var ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>';
761 +var ajaxurl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>';
756 762
757 763 jQuery(document).ready(function($) {
758 764 // Variables to store the current payment being approved
759 765 let currentInvoiceId = null;
@@ -767,13 +773,33 @@
767 773 // Store the data for the current payment
768 774 currentInvoiceId = $(this).data('invoice-id');
769 775 currentPaymentId = $(this).data('payment-id');
770 776 currentNonce = $(this).data('nonce');
777 + currentMode = 'approve';
778 + setModalMode('approve');
771 779
772 780 // Show the approval modal
773 781 $('#payment-approval-modal').removeClass('hidden');
774 782 });
775 783
784 + // Reject uses the same dialog with different copy and a different endpoint.
785 + var currentMode = 'approve';
786 + function setModalMode(mode) {
787 + $('#payment-approval-modal [data-ei-mode-text]').each(function() {
788 + $(this).prop('hidden', $(this).data('ei-mode-text') !== mode);
789 + });
790 + $('#confirm-approve-btn').toggleClass('bg-red-600 hover:bg-red-700', mode === 'reject').toggleClass('bg-green-600 hover:bg-green-700', mode !== 'reject');
791 + }
792 + $('.reject-payment-btn').on('click', function(e) {
793 + e.preventDefault();
794 + currentInvoiceId = null;
795 + currentPaymentId = $(this).data('payment-id');
796 + currentNonce = $(this).data('nonce');
797 + currentMode = 'reject';
798 + setModalMode('reject');
799 + $('#payment-approval-modal').removeClass('hidden');
800 + });
801 +
776 802 // Handle Cancel button in modal
777 803 $('#cancel-approve-btn').on('click', function() {
778 804 // Hide the modal and reset the form
779 805 $('#payment-approval-modal').addClass('hidden');
@@ -785,9 +811,9 @@
785 811 // Get the notes from the textarea
786 812 const notes = $('#approval-notes').val();
787 813
788 814 // Disable the button to prevent double clicks
789 - $(this).prop('disabled', true).text('Processing...');
815 + $(this).prop('disabled', true);
790 816
791 817 // Make AJAX request to approve the payment
792 818 $.ajax({
793 819 url: ajaxurl,
@@ -792,10 +818,11 @@
792 818 $.ajax({
793 819 url: ajaxurl,
794 820 type: 'POST',
795 821 data: {
796 - action: 'easy_invoice_approve_payment',
822 + action: currentMode === 'reject' ? 'easy_invoice_reject_payment' : 'easy_invoice_approve_payment',
797 823 invoice_id: currentInvoiceId,
824 + payment_id: currentPaymentId,
798 825 nonce: currentNonce,
799 826 notes: notes
800 827 },
801 828 success: function(response) {
@@ -801,12 +828,13 @@
801 828 success: function(response) {
802 829 // Hide the modal
803 830 $('#payment-approval-modal').addClass('hidden');
804 831
832 + var toast = response && response.data && response.data.toast ? response.data.toast.message : '';
805 833 if (response.success) {
806 834 // Show success toast
807 835 if (typeof EasyInvoiceToast !== 'undefined') {
808 - EasyInvoiceToast.success('Payment approved successfully.');
836 + EasyInvoiceToast.success(toast || (currentMode === 'reject' ? 'Payment rejected.' : 'Payment confirmed.'));
809 837 }
810 838
811 839 // Refresh the page after a short delay
812 840 setTimeout(function() {
@@ -814,13 +842,13 @@
814 842 }, 1500);
815 843 } else {
816 844 // Show error toast
817 845 if (typeof EasyInvoiceToast !== 'undefined') {
818 - EasyInvoiceToast.error('Failed to approve payment.');
846 + EasyInvoiceToast.error(toast || 'The payment could not be updated.');
819 847 }
820 848
821 849 // Re-enable the button
822 - $('#confirm-approve-btn').prop('disabled', false).text('Approve Payment');
850 + $('#confirm-approve-btn').prop('disabled', false); setModalMode(currentMode);
823 851 }
824 852 },
825 853 error: function() {
826 854 // Hide the modal
@@ -831,9 +859,9 @@
831 859 EasyInvoiceToast.error('Server error occurred. Please try again.');
832 860 }
833 861
834 862 // Re-enable the button
835 - $('#confirm-approve-btn').prop('disabled', false).text('Approve Payment');
863 + $('#confirm-approve-btn').prop('disabled', false); setModalMode(currentMode);
836 864 }
837 865 });
838 866 });
839 867