| @@ -6,8 +6,12 @@ | ||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | namespace EasyInvoice\Templates\Payments; |
| 9 | 9 | |
| 10 | + | |
| 11 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 12 | + exit; | |
| 13 | +} | |
| 10 | 14 | use EasyInvoice\Models\Invoice; |
| 11 | 15 | ?> |
| 12 | 16 | |
| 13 | 17 | <div class="p-8"> |
| @@ -12,88 +16,80 @@ | ||
| 12 | 16 | |
| 13 | 17 | <div class="p-8"> |
| 14 | 18 | <div class="flex justify-between items-center mb-6"> |
| 15 | 19 | <h1 class="text-2xl font-bold text-gray-900"> |
| 16 | - <?php _e('Add New Payment', 'easy-invoice'); ?> | |
| 20 | + <?php esc_html_e('Add New Payment', 'easy-invoice'); ?> | |
| 17 | 21 | </h1> |
| 18 | - <a href="<?php echo admin_url('admin.php?page=easy-invoice-payments'); ?>" | |
| 22 | + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-payments')); ?>" | |
| 19 | 23 | 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"> |
| 20 | - <?php _e('Back to Payments', 'easy-invoice'); ?> | |
| 24 | + <?php esc_html_e('Back to Payments', 'easy-invoice'); ?> | |
| 21 | 25 | </a> |
| 22 | 26 | </div> |
| 23 | 27 | |
| 24 | 28 | <div class="bg-white shadow overflow-hidden sm:rounded-lg"> |
| 25 | - <form method="post" action="<?php echo admin_url('admin-ajax.php'); ?>" id="new-payment-form" class="space-y-6 p-6"> | |
| 29 | + <form method="post" action="<?php echo esc_url(admin_url('admin-ajax.php')); ?>" id="new-payment-form" class="space-y-6 p-6"> | |
| 26 | 30 | <?php wp_nonce_field('easy_invoice_payment', 'payment_nonce'); ?> |
| 27 | - <input type="hidden" name="action" value="easy_invoice_process_payment"> | |
| 31 | + <input type="hidden" name="action" value="easy_invoice_record_payment"> | |
| 28 | 32 | |
| 29 | 33 | <div class="grid grid-cols-1 gap-6 sm:grid-cols-2"> |
| 30 | - <!-- Invoice Selection --> | |
| 31 | 34 | <div> |
| 32 | 35 | <label for="invoice_id" class="block text-sm font-medium text-gray-700"> |
| 33 | - <?php _e('Invoice', 'easy-invoice'); ?> <span class="text-red-500">*</span> | |
| 36 | + <?php esc_html_e('Invoice', 'easy-invoice'); ?> <span class="text-red-500">*</span> | |
| 34 | 37 | </label> |
| 38 | + <input type="search" id="invoice_filter" class="mt-1 mb-2 block w-full border border-gray-300 rounded-md py-2 px-3 sm:text-sm" placeholder="<?php esc_attr_e( 'Type an invoice number or title to filter the list', 'easy-invoice' ); ?>" autocomplete="off"> | |
| 35 | 39 | <select name="invoice_id" id="invoice_id" required |
| 36 | 40 | class="mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"> |
| 37 | - <option value=""><?php _e('Select Invoice', 'easy-invoice'); ?></option> | |
| 41 | + <option value=""><?php esc_html_e('Select Invoice', 'easy-invoice'); ?></option> | |
| 38 | 42 | <?php |
| 39 | - $invoices = get_posts(array( | |
| 40 | - 'post_type' => 'easy_invoice', | |
| 41 | - 'posts_per_page' => -1, | |
| 42 | - 'post_status' => 'publish', | |
| 43 | - 'orderby' => 'date', | |
| 44 | - 'order' => 'DESC', | |
| 45 | - 'meta_query' => array( | |
| 46 | - 'relation' => 'OR', | |
| 47 | - array( | |
| 48 | - 'key' => '_payment_status', | |
| 49 | - 'value' => 'unpaid', | |
| 50 | - 'compare' => '=' | |
| 51 | - ), | |
| 52 | - array( | |
| 53 | - 'key' => '_payment_status', | |
| 54 | - 'compare' => 'NOT EXISTS' | |
| 55 | - ), | |
| 56 | - array( | |
| 57 | - 'key' => '_payment_status', | |
| 58 | - 'value' => '', | |
| 59 | - 'compare' => '=' | |
| 60 | - ) | |
| 61 | - ) | |
| 62 | - )); | |
| 63 | - | |
| 64 | - foreach ($invoices as $post) { | |
| 65 | - $invoice = new Invoice($post); | |
| 66 | - | |
| 67 | - // Get invoice number - try different methods | |
| 68 | - $invoice_number = $invoice->getNumber(); | |
| 69 | - if (empty($invoice_number)) { | |
| 70 | - $invoice_number = get_post_meta($invoice->getId(), '_easy_invoice_number', true); | |
| 43 | + // Only invoices that can still take a payment, newest first, and | |
| 44 | + // never the whole table: a shop with ten thousand invoices used | |
| 45 | + // to get every one of them instantiated into this list. The | |
| 46 | + // balance shown is what is still due, primed in two queries. | |
| 47 | + $ei_open_statuses = [ 'available', 'unpaid', 'overdue', 'partial', 'pending_verification', 'sent' ]; | |
| 48 | + $ei_open_statuses = apply_filters( 'easy_invoice_payment_form_open_statuses', $ei_open_statuses ); | |
| 49 | + $ei_preselect = isset( $_GET['invoice_id'] ) ? absint( $_GET['invoice_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 50 | + $ei_limit = (int) apply_filters( 'easy_invoice_payment_form_invoice_limit', 300 ); | |
| 51 | + $ei_open_ids = get_posts( [ | |
| 52 | + 'post_type' => 'easy_invoice', | |
| 53 | + 'post_status' => 'publish', | |
| 54 | + 'posts_per_page' => $ei_limit, | |
| 55 | + 'orderby' => 'date', | |
| 56 | + 'order' => 'DESC', | |
| 57 | + 'fields' => 'ids', | |
| 58 | + 'meta_query' => [ [ 'key' => '_easy_invoice_status', 'value' => $ei_open_statuses, 'compare' => 'IN' ] ], | |
| 59 | + ] ); | |
| 60 | + if ( $ei_preselect && ! in_array( $ei_preselect, $ei_open_ids, true ) && 'easy_invoice' === get_post_type( $ei_preselect ) ) { | |
| 61 | + array_unshift( $ei_open_ids, $ei_preselect ); | |
| 62 | + } | |
| 63 | + $ei_open_ids = array_map( 'intval', $ei_open_ids ); | |
| 64 | + if ( $ei_open_ids ) { | |
| 65 | + update_meta_cache( 'post', $ei_open_ids ); | |
| 66 | + _prime_post_caches( $ei_open_ids, false, false ); | |
| 67 | + \EasyInvoice\Services\InvoiceBalance::prime( $ei_open_ids ); | |
| 68 | + } | |
| 69 | + foreach ( $ei_open_ids as $ei_id ) { | |
| 70 | + // Read the option straight from the primed meta cache. Building an | |
| 71 | + // Invoice model per row (items, totals) cost ~2 s for 300 options. | |
| 72 | + $invoice_number = (string) get_post_meta( $ei_id, '_easy_invoice_number', true ) ?: 'INV-' . $ei_id; | |
| 73 | + $ei_cached = get_post_meta( $ei_id, \EasyInvoice\Services\InvoiceTotalsCache::META_TOTAL, true ); | |
| 74 | + if ( '' === (string) $ei_cached ) { | |
| 75 | + $ei_model = new Invoice( $ei_id ); // not backfilled yet: compute once and store | |
| 76 | + $ei_cached = $ei_model->getTotal(); | |
| 77 | + \EasyInvoice\Services\InvoiceTotalsCache::store( $ei_model ); | |
| 71 | 78 | } |
| 72 | - if (empty($invoice_number)) { | |
| 73 | - $invoice_number = 'INV-' . $invoice->getId(); | |
| 79 | + $ei_due = max( 0, (float) $ei_cached - \EasyInvoice\Services\InvoiceBalance::paid( $ei_id ) - \EasyInvoice\Services\InvoiceBalance::credited( $ei_id ) ); | |
| 80 | + $ei_currency = strtoupper( (string) get_post_meta( $ei_id, '_easy_invoice_currency_code', true ) ); | |
| 81 | + if ( '' === $ei_currency || 'GLOBAL' === $ei_currency ) { | |
| 82 | + $ei_currency = strtoupper( (string) get_option( 'easy_invoice_currency_code', 'USD' ) ); | |
| 74 | 83 | } |
| 75 | - | |
| 76 | - // Get total amount | |
| 77 | - $total = $invoice->getTotal(); | |
| 78 | - if (empty($total)) { | |
| 79 | - $total = get_post_meta($invoice->getId(), '_easy_invoice_total', true); | |
| 80 | - } | |
| 81 | - if (empty($total)) { | |
| 82 | - $total = 0; | |
| 83 | - } | |
| 84 | - | |
| 85 | - // Get currency symbol | |
| 86 | - $currency_symbol = $invoice->getCurrencySymbol(); | |
| 87 | - if (empty($currency_symbol)) { | |
| 88 | - $currency_symbol = get_option('easy_invoice_currency_symbol', '$'); | |
| 89 | - } | |
| 90 | - | |
| 84 | + $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol( $ei_currency ) ?: get_option( 'easy_invoice_currency_symbol', '$' ); | |
| 85 | + $ei_title = (string) get_post_field( 'post_title', $ei_id ); | |
| 91 | 86 | printf( |
| 92 | - '<option value="%d">%s - %s</option>', | |
| 93 | - $invoice->getId(), | |
| 94 | - esc_html($invoice_number), | |
| 95 | - esc_html($currency_symbol . number_format($total, 2)) | |
| 87 | + '<option value="%d"%s>%s - %s</option>', | |
| 88 | + (int) $ei_id, | |
| 89 | + selected( $ei_preselect, $ei_id, false ), | |
| 90 | + esc_html( $invoice_number . ( '' !== $ei_title && $ei_title !== $invoice_number ? ' · ' . wp_html_excerpt( $ei_title, 40, '…' ) : '' ) ), | |
| 91 | + esc_html( $currency_symbol . number_format( $ei_due, 2 ) ) | |
| 96 | 92 | ); |
| 97 | 93 | } |
| 98 | 94 | ?> |
| 99 | 95 | </select> |
| @@ -98,16 +94,15 @@ | ||
| 98 | 94 | ?> |
| 99 | 95 | </select> |
| 100 | 96 | </div> |
| 101 | 97 | |
| 102 | - <!-- Payment Method --> | |
| 103 | 98 | <div> |
| 104 | 99 | <label for="payment_method" class="block text-sm font-medium text-gray-700"> |
| 105 | - <?php _e('Payment Method', 'easy-invoice'); ?> <span class="text-red-500">*</span> | |
| 100 | + <?php esc_html_e('Payment Method', 'easy-invoice'); ?> <span class="text-red-500">*</span> | |
| 106 | 101 | </label> |
| 107 | 102 | <select name="payment_method" id="payment_method" required |
| 108 | 103 | class="mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"> |
| 109 | - <option value=""><?php _e('Select Payment Method', 'easy-invoice'); ?></option> | |
| 104 | + <option value=""><?php esc_html_e('Select Payment Method', 'easy-invoice'); ?></option> | |
| 110 | 105 | <?php |
| 111 | 106 | // Get available payment gateways (sorted) |
| 112 | 107 | $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager(); |
| 113 | 108 | $enabled_gateways = $gateway_manager->getEnabledGateways(); |
| @@ -112,10 +107,15 @@ | ||
| 112 | 107 | $gateway_manager = \EasyInvoice\EasyInvoice::getInstance()->getGatewayManager(); |
| 113 | 108 | $enabled_gateways = $gateway_manager->getEnabledGateways(); |
| 114 | 109 | |
| 115 | 110 | // Show only enabled gateways and manual options |
| 111 | + // How the money arrived. Cash and cheque are always | |
| 112 | + // possible whether or not a gateway for them is on. | |
| 116 | 113 | $available_methods = [ |
| 117 | - 'manual' => __('Manual', 'easy-invoice'), | |
| 114 | + 'cash' => __('Cash', 'easy-invoice'), | |
| 115 | + 'cheque' => __('Cheque', 'easy-invoice'), | |
| 116 | + 'bank_transfer' => __('Bank transfer', 'easy-invoice'), | |
| 117 | + 'other' => __('Other', 'easy-invoice'), | |
| 118 | 118 | ]; |
| 119 | 119 | |
| 120 | 120 | // Add enabled gateways only (already sorted) |
| 121 | 121 | foreach ($enabled_gateways as $gateway_id => $gateway) { |
| @@ -132,16 +132,15 @@ | ||
| 132 | 132 | ?> |
| 133 | 133 | </select> |
| 134 | 134 | </div> |
| 135 | 135 | |
| 136 | - <!-- Amount --> | |
| 137 | 136 | <div> |
| 138 | 137 | <label for="amount" class="block text-sm font-medium text-gray-700"> |
| 139 | - <?php _e('Amount', 'easy-invoice'); ?> <span class="text-red-500">*</span> | |
| 138 | + <?php esc_html_e('Amount', 'easy-invoice'); ?> <span class="text-red-500">*</span> | |
| 140 | 139 | </label> |
| 141 | 140 | <div class="mt-1 relative rounded-md shadow-sm"> |
| 142 | 141 | <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
| 143 | - <span class="text-gray-500 sm:text-sm"><?php echo get_option('easy_invoice_currency_symbol', '$'); ?></span> | |
| 142 | + <span class="text-gray-500 sm:text-sm"><?php echo esc_html(get_option('easy_invoice_currency_symbol', '$')); ?></span> | |
| 144 | 143 | </div> |
| 145 | 144 | <input type="number" name="amount" id="amount" step="0.01" required |
| 146 | 145 | class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 pl-7 pr-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" |
| 147 | 146 | placeholder="0.00"> |
| @@ -147,27 +146,25 @@ | ||
| 147 | 146 | placeholder="0.00"> |
| 148 | 147 | </div> |
| 149 | 148 | </div> |
| 150 | 149 | |
| 151 | - <!-- Payment Date --> | |
| 152 | 150 | <div> |
| 153 | 151 | <label for="payment_date" class="block text-sm font-medium text-gray-700"> |
| 154 | - <?php _e('Payment Date', 'easy-invoice'); ?> <span class="text-red-500">*</span> | |
| 152 | + <?php esc_html_e('Payment Date', 'easy-invoice'); ?> <span class="text-red-500">*</span> | |
| 155 | 153 | </label> |
| 156 | 154 | <input type="date" name="payment_date" id="payment_date" |
| 157 | - value="<?php echo date('Y-m-d'); ?>" required | |
| 155 | + value="<?php echo esc_attr(current_time('Y-m-d')); ?>" required | |
| 158 | 156 | 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"> |
| 159 | 157 | </div> |
| 160 | 158 | |
| 161 | - <!-- Notes --> | |
| 162 | 159 | <div class="sm:col-span-2"> |
| 163 | 160 | <label for="notes" class="block text-sm font-medium text-gray-700"> |
| 164 | - <?php _e('Notes', 'easy-invoice'); ?> | |
| 161 | + <?php esc_html_e('Notes', 'easy-invoice'); ?> | |
| 165 | 162 | </label> |
| 166 | 163 | <div class="mt-1"> |
| 167 | 164 | <textarea name="notes" id="notes" rows="3" |
| 168 | 165 | 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" |
| 169 | - placeholder="<?php _e('Add any additional notes about this payment...', 'easy-invoice'); ?>"></textarea> | |
| 166 | + placeholder="<?php echo esc_js(__('Add any additional notes about this payment...', 'easy-invoice')); ?>"></textarea> | |
| 170 | 167 | </div> |
| 171 | 168 | </div> |
| 172 | 169 | </div> |
| 173 | 170 | |
| @@ -174,9 +171,9 @@ | ||
| 174 | 171 | <div class="pt-5"> |
| 175 | 172 | <div class="flex justify-end"> |
| 176 | 173 | <button type="submit" |
| 177 | 174 | class="ml-3 inline-flex justify-center py-2 px-4 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"> |
| 178 | - <?php _e('Add Payment', 'easy-invoice'); ?> | |
| 175 | + <?php esc_html_e('Add Payment', 'easy-invoice'); ?> | |
| 179 | 176 | </button> |
| 180 | 177 | </div> |
| 181 | 178 | </div> |
| 182 | 179 | </form> |
| @@ -184,8 +181,14 @@ | ||
| 184 | 181 | </div> |
| 185 | 182 | |
| 186 | 183 | <script> |
| 187 | 184 | jQuery(document).ready(function($) { |
| 185 | + // Filter the (capped) invoice list as the user types. | |
| 186 | + var $invoiceOptions = $('#invoice_id option'); | |
| 187 | + $('#invoice_filter').on('input', function() { | |
| 188 | + var q = $(this).val().toLowerCase(); | |
| 189 | + $invoiceOptions.each(function() { var t = $(this).text().toLowerCase(); $(this).toggle(!q || !this.value || t.indexOf(q) !== -1); }); | |
| 190 | + }); | |
| 188 | 191 | // Update amount when invoice is selected |
| 189 | 192 | $('#invoice_id').on('change', function() { |
| 190 | 193 | var selectedOption = $(this).find('option:selected'); |
| 191 | 194 | var amount = selectedOption.text().split(' - ')[1]; |
| @@ -193,8 +196,10 @@ | ||
| 193 | 196 | $('#amount').val(amount.replace(/[^0-9.-]+/g, '')); |
| 194 | 197 | } |
| 195 | 198 | }); |
| 196 | 199 | |
| 200 | + if ($('#invoice_id').val()) { $('#invoice_id').trigger('change'); } | |
| 201 | + | |
| 197 | 202 | // Form submission |
| 198 | 203 | $('#new-payment-form').on('submit', function(e) { |
| 199 | 204 | e.preventDefault(); |
| 200 | 205 | |
| @@ -207,16 +212,16 @@ | ||
| 207 | 212 | processData: false, |
| 208 | 213 | contentType: false, |
| 209 | 214 | success: function(response) { |
| 210 | 215 | if (response.success) { |
| 211 | - window.location.href = '<?php echo admin_url('admin.php?page=easy-invoice-payments'); ?>'; | |
| 216 | + window.location.href = '<?php echo esc_url(admin_url('admin.php?page=easy-invoice-payments')); ?>'; | |
| 212 | 217 | } else { |
| 213 | - EasyInvoiceToast.error(response.data.message || '<?php _e('An error occurred', 'easy-invoice'); ?>'); | |
| 218 | + EasyInvoiceToast.error(response.data.message || '<?php echo esc_js(__('An error occurred', 'easy-invoice')); ?>'); | |
| 214 | 219 | } |
| 215 | 220 | }, |
| 216 | 221 | error: function() { |
| 217 | - EasyInvoiceToast.error('<?php _e('An error occurred', 'easy-invoice'); ?>'); | |
| 222 | + EasyInvoiceToast.error('<?php echo esc_js(__('An error occurred', 'easy-invoice')); ?>'); | |
| 218 | 223 | } |
| 219 | 224 | }); |
| 220 | 225 | }); |
| 221 | 226 | }); |
| 222 | 227 | </script> |