| @@ -38,17 +38,17 @@ | ||
| 38 | 38 | $text_class = 'text-blue-800'; |
| 39 | 39 | $icon = '<svg class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>'; |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | - echo '<div class="rounded-md ' . $bg_class . ' p-4 mb-4">'; | |
| 42 | + echo '<div class="rounded-md ' . esc_html($bg_class) . ' p-4 mb-4">'; | |
| 43 | 43 | echo '<div class="flex">'; |
| 44 | - echo '<div class="flex-shrink-0">' . $icon . '</div>'; | |
| 44 | + echo '<div class="flex-shrink-0">' . wp_kses_post($icon) . '</div>'; | |
| 45 | 45 | echo '<div class="ml-3">'; |
| 46 | - echo '<p class="text-sm font-medium ' . $text_class . '">' . $message . '</p>'; | |
| 46 | + echo '<p class="text-sm font-medium ' . esc_html($text_class) . '">' . esc_html($message) . '</p>'; | |
| 47 | 47 | echo '</div>'; |
| 48 | 48 | echo '<div class="ml-auto pl-3">'; |
| 49 | 49 | echo '<div class="-mx-1.5 -my-1.5">'; |
| 50 | - echo '<button type="button" class="inline-flex rounded-md p-1.5 ' . $text_class . ' hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 notification-dismiss">'; | |
| 50 | + echo '<button type="button" class="inline-flex rounded-md p-1.5 ' . esc_html($text_class) . ' hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 notification-dismiss">'; | |
| 51 | 51 | echo '<span class="sr-only">Dismiss</span>'; |
| 52 | 52 | echo '<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">'; |
| 53 | 53 | echo '<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />'; |
| 54 | 54 | echo '</svg>'; |
| @@ -68,9 +68,10 @@ | ||
| 68 | 68 | if ($updated_count > 0) { |
| 69 | 69 | // Show a one-time notification |
| 70 | 70 | add_action('admin_notices', function() use ($updated_count) { |
| 71 | 71 | echo '<div class="notice notice-success is-dismissible">'; |
| 72 | - echo '<p>' . sprintf(__('%d draft invoices updated to published status for proper permalinks.', 'easy-invoice'), $updated_count) . '</p>'; | |
| 72 | + /* translators: %d: number updated. */ | |
| 73 | + echo '<p>' . sprintf(esc_html__('%d draft invoices updated to published status for proper permalinks.', 'easy-invoice'), (int) $updated_count) . '</p>'; | |
| 73 | 74 | echo '</div>'; |
| 74 | 75 | }); |
| 75 | 76 | } |
| 76 | 77 | |
| @@ -86,13 +87,18 @@ | ||
| 86 | 87 | |
| 87 | 88 | // Get recurring filter |
| 88 | 89 | $recurring_filter = isset($_GET['recurring']) ? sanitize_text_field($_GET['recurring']) : ''; |
| 89 | 90 | |
| 91 | +// Get client filter (from template data, falls back to GET) | |
| 92 | +$client_filter = isset($client_filter) ? (int) $client_filter : (isset($_GET['client_id']) ? absint($_GET['client_id']) : 0); | |
| 93 | +$clients_list = isset($clients_list) && is_array($clients_list) ? $clients_list : []; | |
| 94 | + | |
| 90 | 95 | // Check for bulk action notifications |
| 91 | 96 | $notification = false; |
| 92 | 97 | |
| 93 | 98 | if (isset($_GET['bulk_trashed']) && $_GET['bulk_trashed'] > 0) { |
| 94 | 99 | $count = intval($_GET['bulk_trashed']); |
| 100 | + /* translators: %s: number of items. */ | |
| 95 | 101 | easy_invoice_display_notification('success', sprintf(_n('%s invoice moved to trash.', '%s invoices moved to trash.', $count, 'easy-invoice'), $count)); |
| 96 | 102 | $notification = true; |
| 97 | 103 | } |
| 98 | 104 | |
| @@ -97,8 +103,9 @@ | ||
| 97 | 103 | } |
| 98 | 104 | |
| 99 | 105 | if (isset($_GET['bulk_restored']) && $_GET['bulk_restored'] > 0) { |
| 100 | 106 | $count = intval($_GET['bulk_restored']); |
| 107 | + /* translators: %s: number of items. */ | |
| 101 | 108 | easy_invoice_display_notification('success', sprintf(_n('%s invoice restored from trash.', '%s invoices restored from trash.', $count, 'easy-invoice'), $count)); |
| 102 | 109 | $notification = true; |
| 103 | 110 | } |
| 104 | 111 | |
| @@ -103,8 +110,9 @@ | ||
| 103 | 110 | } |
| 104 | 111 | |
| 105 | 112 | if (isset($_GET['bulk_deleted']) && $_GET['bulk_deleted'] > 0) { |
| 106 | 113 | $count = intval($_GET['bulk_deleted']); |
| 114 | + /* translators: %s: number of items. */ | |
| 107 | 115 | easy_invoice_display_notification('success', sprintf(_n('%s invoice permanently deleted.', '%s invoices permanently deleted.', $count, 'easy-invoice'), $count)); |
| 108 | 116 | $notification = true; |
| 109 | 117 | } |
| 110 | 118 | |
| @@ -109,8 +117,9 @@ | ||
| 109 | 117 | } |
| 110 | 118 | |
| 111 | 119 | if (isset($_GET['bulk_drafted']) && $_GET['bulk_drafted'] > 0) { |
| 112 | 120 | $count = intval($_GET['bulk_drafted']); |
| 121 | + /* translators: %s: number of items. */ | |
| 113 | 122 | easy_invoice_display_notification('success', sprintf(_n('%s invoice set to draft status.', '%s invoices set to draft status.', $count, 'easy-invoice'), $count)); |
| 114 | 123 | $notification = true; |
| 115 | 124 | } |
| 116 | 125 | |
| @@ -115,8 +124,9 @@ | ||
| 115 | 124 | } |
| 116 | 125 | |
| 117 | 126 | if (isset($_GET['bulk_published']) && $_GET['bulk_published'] > 0) { |
| 118 | 127 | $count = intval($_GET['bulk_published']); |
| 128 | + /* translators: %s: number of items. */ | |
| 119 | 129 | easy_invoice_display_notification('success', sprintf(_n('%s invoice published successfully.', '%s invoices published successfully.', $count, 'easy-invoice'), $count)); |
| 120 | 130 | $notification = true; |
| 121 | 131 | } |
| 122 | 132 | |
| @@ -156,64 +166,98 @@ | ||
| 156 | 166 | // Get stats from all invoices, not just the current page |
| 157 | 167 | $invoice_controller = new \EasyInvoice\Controllers\InvoiceController(); |
| 158 | 168 | $stats = $invoice_controller->getInvoiceStats(); |
| 159 | 169 | |
| 160 | -// Add total invoices count from current page for display purposes | |
| 161 | -$stats['total_invoices'] = count($formatted_invoices); | |
| 170 | +// The controller passes the real counts (found_posts and SQL counts); the | |
| 171 | +// template used to overwrite the total with the size of the current page and | |
| 172 | +// load every trashed / draft invoice as a model just to count them. | |
| 173 | +$all_count = (int) ($stats['total_invoices'] ?? 0); | |
| 174 | +if (!isset($trash_count)) { | |
| 175 | + $trash_count = (int) $repository->count(['post_status' => 'trash']); | |
| 176 | +} | |
| 177 | +if (!isset($draft_count)) { | |
| 178 | + $draft_count = (int) $repository->count(['meta_key' => '_easy_invoice_status', 'meta_value' => 'draft']); // phpcs:ignore WordPress.DB.SlowDBQuery | |
| 179 | +} | |
| 162 | 180 | |
| 163 | -// Get trash count for tab display | |
| 164 | -$trash_args = ['post_status' => 'trash']; | |
| 165 | -$trash_invoices = $repository->all($trash_args); | |
| 166 | -$trash_count = count($trash_invoices); | |
| 167 | - | |
| 168 | -// Get draft count for tab display | |
| 169 | -$draft_args = ['post_status' => 'draft']; | |
| 170 | -$draft_invoices = $repository->all($draft_args); | |
| 171 | -$draft_count = count($draft_invoices); | |
| 172 | - | |
| 173 | 181 | // Define available status filters |
| 182 | +// The statuses an invoice can actually hold — "Pending" was offered but no | |
| 183 | +// invoice is ever in that state, while "Available" (every new invoice) was not. | |
| 174 | 184 | $status_filters = array( |
| 175 | - 'paid' => 'Paid', | |
| 176 | - 'unpaid' => 'Unpaid', | |
| 177 | - 'pending' => 'Pending', | |
| 178 | - 'draft' => 'Draft', | |
| 179 | - 'overdue' => 'Overdue', | |
| 180 | - 'cancelled' => 'Cancelled' | |
| 185 | + 'available' => __('Available', 'easy-invoice'), | |
| 186 | + 'paid' => __('Paid', 'easy-invoice'), | |
| 187 | + 'partial' => __('Partially paid', 'easy-invoice'), | |
| 188 | + 'unpaid' => __('Unpaid', 'easy-invoice'), | |
| 189 | + 'overdue' => __('Overdue', 'easy-invoice'), | |
| 190 | + 'draft' => __('Draft', 'easy-invoice'), | |
| 191 | + 'cancelled' => __('Cancelled', 'easy-invoice'), | |
| 181 | 192 | ); |
| 182 | 193 | |
| 183 | 194 | ?> |
| 184 | 195 | <div class="p-8"> |
| 185 | - <!-- Page Header --> | |
| 186 | 196 | <div class="ei-app-page-header bg-white border-b border-gray-200 px-6" style="margin-left: -2rem; margin-top: -2rem; padding-left:2rem; padding-right:2rem; margin-right: -2rem;"> |
| 187 | 197 | <div class="flex items-center justify-between"> |
| 188 | 198 | <div> |
| 189 | - <h1 class="text-2xl font-bold text-gray-900"><?php _e('Invoices', 'easy-invoice'); ?></h1> | |
| 190 | - <p class="mt-1 text-sm text-gray-500"><?php _e('Manage and track your invoices', 'easy-invoice'); ?></p> | |
| 199 | + <h1 class="text-2xl font-bold text-gray-900"><?php esc_html_e('Invoices', 'easy-invoice'); ?></h1> | |
| 200 | + <p class="mt-1 text-sm text-gray-500"><?php esc_html_e('Manage and track your invoices', 'easy-invoice'); ?></p> | |
| 191 | 201 | </div> |
| 192 | 202 | <div class="flex items-center space-x-3"> |
| 193 | 203 | <?php if (isset($_GET['rewrite_flushed'])): ?> |
| 194 | 204 | <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-2 rounded-md text-sm"> |
| 195 | - <?php _e('Rewrite rules flushed successfully!', 'easy-invoice'); ?> | |
| 205 | + <?php esc_html_e('Rewrite rules flushed successfully!', 'easy-invoice'); ?> | |
| 196 | 206 | </div> |
| 197 | 207 | <?php endif; ?> |
| 198 | 208 | |
| 199 | 209 | <?php if (isset($_GET['post_types_registered'])): ?> |
| 200 | 210 | <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-2 rounded-md text-sm"> |
| 201 | - <?php _e('Post types registered successfully!', 'easy-invoice'); ?> | |
| 211 | + <?php esc_html_e('Post types registered successfully!', 'easy-invoice'); ?> | |
| 202 | 212 | </div> |
| 203 | 213 | <?php endif; ?> |
| 204 | 214 | |
| 205 | - <a href="<?php echo wp_nonce_url(admin_url('admin-post.php?action=flush_easy_invoice_rewrite_rules'), 'flush_easy_invoice_rewrite_rules'); ?>" | |
| 215 | + <?php | |
| 216 | + // "Flush Rewrite Rules" is a troubleshooting action, not something a | |
| 217 | + // user does as part of their job — but it rendered unconditionally as | |
| 218 | + // a prominent button right beside the primary "Create New Invoice" CTA | |
| 219 | + // on every admin's screen. It is now hidden by default and shown when | |
| 220 | + // WP_DEBUG is on, or when a site opts in via the filter. | |
| 221 | + // | |
| 222 | + // Kept rather than deleted because it is the manual escape hatch if | |
| 223 | + // invoice permalinks ever 404. | |
| 224 | + $ei_show_maintenance_tools = apply_filters( | |
| 225 | + 'easy_invoice_show_maintenance_tools', | |
| 226 | + defined('WP_DEBUG') && WP_DEBUG | |
| 227 | + ); | |
| 228 | + if ($ei_show_maintenance_tools) : ?> | |
| 229 | + <a href="<?php echo esc_url(wp_nonce_url(admin_url('admin-post.php?action=flush_easy_invoice_rewrite_rules'), 'flush_easy_invoice_rewrite_rules')); ?>" | |
| 206 | 230 | class="bg-yellow-100 text-yellow-700 hover:bg-yellow-200 px-4 py-2 rounded-md text-sm font-medium transition-colors duration-200"> |
| 207 | - <?php _e('Flush Rewrite Rules', 'easy-invoice'); ?> | |
| 231 | + <?php esc_html_e('Flush Rewrite Rules', 'easy-invoice'); ?> | |
| 208 | 232 | </a> |
| 233 | + <?php endif; ?> | |
| 209 | 234 | |
| 210 | - <?php if (!easy_invoice_has_pro()): ?> | |
| 235 | + <?php | |
| 236 | + // "Export All" is part of the bulk_operations addon | |
| 237 | + // (Personal tier). Gate three states: | |
| 238 | + // addon loaded → working POST form | |
| 239 | + // free user (no Pro) → upsell button + modal | |
| 240 | + // Pro w/o addon active → hidden (enable in Addons grid) | |
| 241 | + $ei_bulk_ops_loaded = \EasyInvoice\Addons\AddonManager::shouldLoad('bulk_operations'); | |
| 242 | + ?> | |
| 243 | + <?php if ($ei_bulk_ops_loaded): ?> | |
| 244 | + <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;"> | |
| 245 | + <input type="hidden" name="action" value="easy_invoice_export_all_invoices" /> | |
| 246 | + <input type="hidden" name="_wpnonce" value="<?php echo esc_attr(wp_create_nonce('easy_invoice_export_all_invoices')); ?>" /> | |
| 247 | + <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"> | |
| 248 | + <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"> | |
| 249 | + <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> | |
| 250 | + </svg> | |
| 251 | + <?php esc_html_e('Export All', 'easy-invoice'); ?> | |
| 252 | + </button> | |
| 253 | + </form> | |
| 254 | + <?php elseif (!easy_invoice_has_pro()): ?> | |
| 211 | 255 | <button type="button" id="export-all-invoices-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"> |
| 212 | 256 | <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"> |
| 213 | 257 | <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> |
| 214 | 258 | </svg> |
| 215 | - <?php _e('Export All', 'easy-invoice'); ?> | |
| 259 | + <?php esc_html_e('Export All', 'easy-invoice'); ?> | |
| 216 | 260 | <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"> |
| 217 | 261 | <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"/> |
| 218 | 262 | </svg> |
| 219 | 263 | </button> |
| @@ -220,41 +264,29 @@ | ||
| 220 | 264 | <script> |
| 221 | 265 | jQuery(document).ready(function($) { |
| 222 | 266 | $('#export-all-invoices-btn').on('click', function(e) { |
| 223 | 267 | e.preventDefault(); |
| 224 | - // Show premium popup using the proper confirmation modal | |
| 225 | 268 | if (typeof EasyInvoiceConfirmation !== 'undefined') { |
| 226 | 269 | EasyInvoiceConfirmation.showFeatureUpgrade('Export All Invoices', 'Export all your invoices to CSV, Excel, or PDF format for backup, analysis, or sharing with your team.'); |
| 227 | 270 | } else { |
| 228 | - // Fallback to alert if confirmation modal is not available | |
| 229 | 271 | alert('<?php echo esc_js(__('Exporting all invoices is a premium feature. Upgrade to Easy Invoice Pro to unlock this feature.', 'easy-invoice')); ?>'); |
| 230 | 272 | } |
| 231 | 273 | }); |
| 232 | 274 | }); |
| 233 | 275 | </script> |
| 234 | - <?php else: ?> | |
| 235 | - <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;"> | |
| 236 | - <input type="hidden" name="action" value="easy_invoice_export_all_invoices" /> | |
| 237 | - <input type="hidden" name="_wpnonce" value="<?php echo wp_create_nonce('easy_invoice_export_all_invoices'); ?>" /> | |
| 238 | - <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"> | |
| 239 | - <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"> | |
| 240 | - <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> | |
| 241 | - </svg> | |
| 242 | - <?php _e('Export All', 'easy-invoice'); ?> | |
| 243 | - </button> | |
| 244 | - </form> | |
| 245 | 276 | <?php endif; ?> |
| 246 | - <a href="#" id="create-new-invoice-btn" 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"> | |
| 277 | + <?php if (easy_invoice_user_can('ei_create_invoice')) : ?> | |
| 278 | +<a href="#" id="create-new-invoice-btn" 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"> | |
| 247 | 279 | <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| 248 | 280 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path> |
| 249 | 281 | </svg> |
| 250 | - <?php _e('Create New Invoice', 'easy-invoice'); ?> | |
| 282 | + <?php esc_html_e('Create New Invoice', 'easy-invoice'); ?> | |
| 251 | 283 | </a> |
| 284 | +<?php endif; ?> | |
| 252 | 285 | </div> |
| 253 | 286 | </div> |
| 254 | 287 | </div> |
| 255 | 288 | |
| 256 | - <!-- Stats Cards --> | |
| 257 | 289 | <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-8 mt-8"> |
| 258 | 290 | <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1"> |
| 259 | 291 | <div class="flex items-center"> |
| 260 | 292 | <div class="rounded-full bg-indigo-100 p-3 mr-4"> |
| @@ -262,10 +294,10 @@ | ||
| 262 | 294 | <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> |
| 263 | 295 | </svg> |
| 264 | 296 | </div> |
| 265 | 297 | <div> |
| 266 | - <h3 class="text-sm font-medium text-gray-500">Total Invoices</h3> | |
| 267 | - <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo $stats['total_invoices']; ?></p> | |
| 298 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Invoices', 'easy-invoice'); ?></h3> | |
| 299 | + <p class="mt-1 text-2xl font-bold text-gray-900"><?php echo esc_html($stats['total_invoices']); ?></p> | |
| 268 | 300 | </div> |
| 269 | 301 | </div> |
| 270 | 302 | </div> |
| 271 | 303 | <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1"> |
| @@ -275,9 +307,9 @@ | ||
| 275 | 307 | <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> |
| 276 | 308 | </svg> |
| 277 | 309 | </div> |
| 278 | 310 | <div> |
| 279 | - <h3 class="text-sm font-medium text-gray-500">Total Revenue</h3> | |
| 311 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Revenue', 'easy-invoice'); ?></h3> | |
| 280 | 312 | <?php if (is_array($stats['total_revenue']) && !empty($stats['total_revenue'])): ?> |
| 281 | 313 | <div class="mt-1"> |
| 282 | 314 | <?php foreach ($stats['total_revenue'] as $currency => $data): ?> |
| 283 | 315 | <?php |
| @@ -286,10 +318,10 @@ | ||
| 286 | 318 | $temp_invoice->setCurrencyCode($currency); |
| 287 | 319 | $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($temp_invoice); |
| 288 | 320 | ?> |
| 289 | 321 | <p class="text-lg font-bold text-gray-900"> |
| 290 | - <?php echo $formatter->format($data['amount']); ?> | |
| 291 | - <span class="text-sm font-normal text-gray-500"><?php echo $currency; ?></span> | |
| 322 | + <?php echo esc_html($formatter->format($data['amount'])); ?> | |
| 323 | + <span class="text-sm font-normal text-gray-500"><?php echo esc_html($currency); ?></span> | |
| 292 | 324 | </p> |
| 293 | 325 | <?php endforeach; ?> |
| 294 | 326 | </div> |
| 295 | 327 | <?php else: ?> |
| @@ -305,19 +337,18 @@ | ||
| 305 | 337 | <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> |
| 306 | 338 | </svg> |
| 307 | 339 | </div> |
| 308 | 340 | <div> |
| 309 | - <h3 class="text-sm font-medium text-gray-500">Total Value</h3> | |
| 341 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Total Value', 'easy-invoice'); ?></h3> | |
| 310 | 342 | <?php if (is_array($stats['total_value']) && !empty($stats['total_value'])): ?> |
| 311 | 343 | <div class="mt-1"> |
| 312 | 344 | <?php foreach ($stats['total_value'] as $currency => $data): ?> |
| 313 | 345 | <?php |
| 314 | - $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($data['invoice_object']); | |
| 315 | - $formatted_amount = $formatter->format($data['amount']); | |
| 346 | + $formatted_amount = easy_invoice_format_money((float) $data['amount'], null, (string) $currency); | |
| 316 | 347 | ?> |
| 317 | 348 | <div class="text-lg font-bold text-gray-900"> |
| 318 | - <?php echo $formatted_amount; ?> | |
| 319 | - <span class="text-xs font-normal text-gray-500 ml-1">(<?php echo $data['invoices']; ?> <?php echo $data['invoices'] > 1 ? 'invoices' : 'invoice'; ?>)</span> | |
| 349 | + <?php echo esc_html($formatted_amount); ?> | |
| 350 | + <span class="text-xs font-normal text-gray-500 ml-1">(<?php echo esc_html($data['invoices']); ?> <?php echo $data['invoices'] > 1 ? 'invoices' : 'invoice'; ?>)</span> | |
| 320 | 351 | </div> |
| 321 | 352 | <?php endforeach; ?> |
| 322 | 353 | </div> |
| 323 | 354 | <?php else: ?> |
| @@ -333,10 +364,10 @@ | ||
| 333 | 364 | <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> |
| 334 | 365 | </svg> |
| 335 | 366 | </div> |
| 336 | 367 | <div> |
| 337 | - <h3 class="text-sm font-medium text-gray-500">Paid Invoices</h3> | |
| 338 | - <p class="mt-1 text-2xl font-bold text-green-600"><?php echo $stats['paid_invoices']; ?></p> | |
| 368 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Paid Invoices', 'easy-invoice'); ?></h3> | |
| 369 | + <p class="mt-1 text-2xl font-bold text-green-600"><?php echo esc_html($stats['paid_invoices']); ?></p> | |
| 339 | 370 | </div> |
| 340 | 371 | </div> |
| 341 | 372 | </div> |
| 342 | 373 | <div class="bg-white rounded-lg shadow p-6 transition-transform duration-300 hover:shadow-md hover:-translate-y-1"> |
| @@ -346,16 +377,15 @@ | ||
| 346 | 377 | <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> |
| 347 | 378 | </svg> |
| 348 | 379 | </div> |
| 349 | 380 | <div> |
| 350 | - <h3 class="text-sm font-medium text-gray-500">Pending Invoices</h3> | |
| 351 | - <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo $stats['pending_invoices']; ?></p> | |
| 381 | + <h3 class="text-sm font-medium text-gray-500"><?php esc_html_e('Outstanding', 'easy-invoice'); ?></h3> | |
| 382 | + <p class="mt-1 text-2xl font-bold text-yellow-600"><?php echo esc_html($stats['pending_invoices']); ?></p> | |
| 352 | 383 | </div> |
| 353 | 384 | </div> |
| 354 | 385 | </div> |
| 355 | 386 | </div> |
| 356 | 387 | |
| 357 | - <!-- Top Pagination --> | |
| 358 | 388 | <?php if ($total_invoices > 0): ?> |
| 359 | 389 | <div class="mb-4"> |
| 360 | 390 | <div class="px-4 py-3 flex items-center justify-between"> |
| 361 | 391 | <div class="flex items-center space-x-2"> |
| @@ -386,12 +416,13 @@ | ||
| 386 | 416 | 'mid_size' => 2, |
| 387 | 417 | 'add_args' => array( |
| 388 | 418 | 'view' => $current_view, |
| 389 | 419 | 'status' => $status_filter, |
| 390 | - 'recurring' => $recurring_filter | |
| 420 | + 'recurring' => $recurring_filter, | |
| 421 | + 'client_id' => $client_filter ?: false, | |
| 391 | 422 | ) |
| 392 | 423 | ); |
| 393 | - | |
| 424 | + | |
| 394 | 425 | $top_pagination_links = paginate_links($top_pagination_args); |
| 395 | 426 | |
| 396 | 427 | if ($top_pagination_links) { |
| 397 | 428 | echo '<div class="flex space-x-1">'; |
| @@ -412,9 +443,9 @@ | ||
| 412 | 443 | if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) { |
| 413 | 444 | $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link); |
| 414 | 445 | } |
| 415 | 446 | |
| 416 | - echo $link; | |
| 447 | + echo wp_kses_post($link); | |
| 417 | 448 | } |
| 418 | 449 | echo '</div>'; |
| 419 | 450 | } |
| 420 | 451 | ?> |
| @@ -423,17 +454,15 @@ | ||
| 423 | 454 | </div> |
| 424 | 455 | </div> |
| 425 | 456 | <?php endif; ?> |
| 426 | 457 | |
| 427 | - <!-- View Tabs and Filters --> | |
| 428 | 458 | <div class="bg-white rounded-lg shadow mb-6 overflow-hidden"> |
| 429 | - <!-- Tabs with more modern styling --> | |
| 430 | 459 | <div class="border-b border-gray-200 bg-gradient-to-r from-indigo-50 to-white"> |
| 431 | 460 | <nav class="flex"> |
| 432 | 461 | <?php |
| 433 | 462 | // View Tabs (All, Draft, Trash) |
| 434 | 463 | $views = array( |
| 435 | - 'all' => 'All (' . $stats['total_invoices'] . ')', | |
| 464 | + 'all' => 'All (' . $all_count . ')', | |
| 436 | 465 | 'draft' => 'Draft (' . $draft_count . ')', |
| 437 | 466 | 'trash' => 'Trash (' . $trash_count . ')' |
| 438 | 467 | ); |
| 439 | 468 | |
| @@ -439,14 +468,17 @@ | ||
| 439 | 468 | |
| 440 | 469 | foreach ($views as $view => $label) { |
| 441 | 470 | $is_current = $current_view === $view; |
| 442 | 471 | $status_param = !empty($status_filter) && $view === 'all' ? '&status=' . $status_filter : ''; |
| 472 | + if (!empty($client_filter)) { | |
| 473 | + $status_param .= '&client_id=' . (int) $client_filter; | |
| 474 | + } | |
| 443 | 475 | $class = $is_current |
| 444 | 476 | ? 'border-b-2 border-indigo-500 text-indigo-600 bg-white' |
| 445 | 477 | : 'border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'; |
| 446 | 478 | ?> |
| 447 | 479 | <a href="?page=easy-invoice-all&view=<?php echo esc_attr($view . $status_param); ?>" |
| 448 | - class="flex items-center whitespace-nowrap py-4 px-6 font-medium text-sm transition-colors duration-200 <?php echo $class; ?>"> | |
| 480 | + class="flex items-center whitespace-nowrap py-4 px-6 font-medium text-sm transition-colors duration-200 <?php echo esc_attr($class); ?>"> | |
| 449 | 481 | <?php if ($view === 'all'): ?> |
| 450 | 482 | <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| 451 | 483 | <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> |
| 452 | 484 | </svg> |
| @@ -458,9 +490,9 @@ | ||
| 458 | 490 | <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| 459 | 491 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path> |
| 460 | 492 | </svg> |
| 461 | 493 | <?php endif; ?> |
| 462 | - <?php echo $label; ?> | |
| 494 | + <?php echo esc_html($label); ?> | |
| 463 | 495 | </a> |
| 464 | 496 | <?php |
| 465 | 497 | } |
| 466 | 498 | ?> |
| @@ -466,38 +498,38 @@ | ||
| 466 | 498 | ?> |
| 467 | 499 | </nav> |
| 468 | 500 | </div> |
| 469 | 501 | |
| 470 | - <!-- Filters and Bulk Actions with Search --> | |
| 471 | 502 | <div class="p-4 flex flex-wrap items-center justify-between bg-white border-b border-gray-100"> |
| 472 | - <!-- Bulk Actions (Left Side) --> | |
| 473 | 503 | <div class="flex items-center space-x-2 my-2"> |
| 474 | - <form id="bulk-action-form" method="post" class="flex items-center gap-2"> | |
| 504 | + <?php if (easy_invoice_user_can('ei_create_invoice')) : ?> | |
| 505 | +<form id="bulk-action-form" method="post" class="flex items-center gap-2"> | |
| 475 | 506 | <?php wp_nonce_field('easy_invoice_bulk_action', 'easy_invoice_bulk_nonce'); ?> |
| 476 | 507 | <input type="hidden" name="action" value="easy_invoice_bulk_action"> |
| 477 | 508 | |
| 478 | - <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"> | |
| 479 | - <option value="">Bulk Actions</option> | |
| 509 | + <select name="bulk_action" aria-label="Bulk action for selected invoices" 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"> | |
| 510 | + <option value=""><?php esc_html_e('Bulk Actions', 'easy-invoice'); ?></option> | |
| 480 | 511 | <?php if ($current_view === 'trash'): ?> |
| 481 | - <option value="restore">Restore</option> | |
| 482 | - <option value="delete">Delete Permanently</option> | |
| 512 | + <option value="restore"><?php esc_html_e('Restore', 'easy-invoice'); ?></option> | |
| 513 | + <option value="delete"><?php esc_html_e('Delete Permanently', 'easy-invoice'); ?></option> | |
| 483 | 514 | <?php elseif ($current_view === 'draft'): ?> |
| 484 | - <option value="publish">Publish</option> | |
| 485 | - <option value="trash">Move to Trash</option> | |
| 515 | + <option value="publish"><?php esc_html_e('Publish', 'easy-invoice'); ?></option> | |
| 516 | + <option value="trash"><?php esc_html_e('Move to Trash', 'easy-invoice'); ?></option> | |
| 486 | 517 | <?php else: ?> |
| 487 | - <option value="trash">Move to Trash</option> | |
| 488 | - <option value="draft">Set to Draft</option> | |
| 518 | + <option value="trash"><?php esc_html_e('Move to Trash', 'easy-invoice'); ?></option> | |
| 519 | + <option value="draft"><?php esc_html_e('Set to Draft', 'easy-invoice'); ?></option> | |
| 489 | 520 | <?php endif; ?> |
| 521 | + <option value="export" data-requires-pro="1"><?php esc_html_e('Export Selected (Pro)', 'easy-invoice'); ?></option> | |
| 490 | 522 | </select> |
| 491 | 523 | |
| 492 | - <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"> | |
| 524 | + <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 whitespace-nowrap"> | |
| 493 | 525 | Apply |
| 494 | 526 | </button> |
| 495 | 527 | </form> |
| 528 | +<?php endif; ?> | |
| 496 | 529 | </div> |
| 497 | 530 | |
| 498 | 531 | <?php if ($current_view !== 'trash'): ?> |
| 499 | - <!-- Search Form (Right Side) --> | |
| 500 | 532 | <div class="flex items-center gap-2"> |
| 501 | 533 | <form method="get" class="flex items-center gap-2"> |
| 502 | 534 | <input type="hidden" name="page" value="easy-invoice-all"> |
| 503 | 535 | <input type="hidden" name="view" value="<?php echo esc_attr($current_view); ?>"> |
| @@ -506,8 +538,21 @@ | ||
| 506 | 538 | <?php endif; ?> |
| 507 | 539 | <?php if (!empty($recurring_filter)): ?> |
| 508 | 540 | <input type="hidden" name="recurring" value="<?php echo esc_attr($recurring_filter); ?>"> |
| 509 | 541 | <?php endif; ?> |
| 542 | + | |
| 543 | + <?php if (!empty($clients_list)): ?> | |
| 544 | + <select name="client_id" aria-label="<?php esc_attr_e('Filter by client', 'easy-invoice'); ?>" | |
| 545 | + class="block px-3 py-2 border border-gray-300 rounded-md bg-white text-gray-700 sm:text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500" | |
| 546 | + onchange="this.form.submit()"> | |
| 547 | + <option value=""><?php esc_html_e('All clients', 'easy-invoice'); ?></option> | |
| 548 | + <?php foreach ($clients_list as $client_option): ?> | |
| 549 | + <option value="<?php echo esc_attr($client_option['id']); ?>" <?php selected((int) $client_filter, (int) $client_option['id']); ?>> | |
| 550 | + <?php echo esc_html($client_option['name']); ?> | |
| 551 | + </option> | |
| 552 | + <?php endforeach; ?> | |
| 553 | + </select> | |
| 554 | + <?php endif; ?> | |
| 510 | 555 | |
| 511 | 556 | <div class="relative"> |
| 512 | 557 | <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
| 513 | 558 | <svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| @@ -516,19 +561,19 @@ | ||
| 516 | 561 | </div> |
| 517 | 562 | <input type="text" |
| 518 | 563 | name="search" |
| 519 | 564 | value="<?php echo esc_attr($search_query); ?>" |
| 520 | - placeholder="<?php _e('Search invoices, numbers, or clients...', 'easy-invoice'); ?>" | |
| 565 | + placeholder="<?php echo esc_js(__('Search invoices, numbers, or clients...', 'easy-invoice')); ?>" | |
| 521 | 566 | class="block w-64 pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"> |
| 522 | 567 | </div> |
| 523 | 568 | |
| 524 | 569 | <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"> |
| 525 | - <?php _e('Search', 'easy-invoice'); ?> | |
| 570 | + <?php esc_html_e('Search', 'easy-invoice'); ?> | |
| 526 | 571 | </button> |
| 527 | 572 | |
| 528 | 573 | <?php if (!empty($search_query)): ?> |
| 529 | 574 | <a href="<?php echo esc_url(remove_query_arg('search', $_SERVER['REQUEST_URI'])); ?>" class="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"> |
| 530 | - <?php _e('Clear', 'easy-invoice'); ?> | |
| 575 | + <?php esc_html_e('Clear', 'easy-invoice'); ?> | |
| 531 | 576 | </a> |
| 532 | 577 | <?php endif; ?> |
| 533 | 578 | </form> |
| 534 | 579 | </div> |
| @@ -534,20 +579,22 @@ | ||
| 534 | 579 | </div> |
| 535 | 580 | <?php endif; ?> |
| 536 | 581 | </div> |
| 537 | 582 | |
| 538 | - <!-- Status Filters --> | |
| 539 | 583 | <?php if ($current_view !== 'trash'): ?> |
| 540 | 584 | <div class="p-4 bg-white border-b border-gray-100"> |
| 541 | 585 | <div class="flex flex-wrap items-center gap-2 my-2"> |
| 542 | - <span class="text-sm text-gray-500 mr-2">Filter by status:</span> | |
| 586 | + <span class="text-sm text-gray-500 mr-2"><?php esc_html_e('Filter by status:', 'easy-invoice'); ?></span> | |
| 543 | 587 | <div class="flex flex-wrap gap-1"> |
| 544 | 588 | <?php |
| 545 | - // Build base URL with current view, recurring filter, and search query | |
| 589 | + // Build base URL with current view, recurring filter, client filter and search query | |
| 546 | 590 | $base_url = '?page=easy-invoice-all&view=' . $current_view; |
| 547 | 591 | if (!empty($recurring_filter)) { |
| 548 | 592 | $base_url .= '&recurring=' . urlencode($recurring_filter); |
| 549 | 593 | } |
| 594 | + if (!empty($client_filter)) { | |
| 595 | + $base_url .= '&client_id=' . (int) $client_filter; | |
| 596 | + } | |
| 550 | 597 | if (!empty($search_query)) { |
| 551 | 598 | $base_url .= '&search=' . urlencode($search_query); |
| 552 | 599 | } |
| 553 | 600 | ?> |
| @@ -556,9 +603,11 @@ | ||
| 556 | 603 | </a> |
| 557 | 604 | |
| 558 | 605 | <?php |
| 559 | 606 | $status_colors = [ |
| 607 | + 'available' => 'bg-blue-100 text-blue-800 ring-1 ring-blue-300', | |
| 560 | 608 | 'paid' => 'bg-green-100 text-green-800 ring-1 ring-green-300', |
| 609 | + 'partial' => 'bg-yellow-100 text-yellow-800 ring-1 ring-yellow-300', | |
| 561 | 610 | 'unpaid' => 'bg-yellow-100 text-yellow-800 ring-1 ring-yellow-300', |
| 562 | 611 | 'pending' => 'bg-blue-100 text-blue-800 ring-1 ring-blue-300', |
| 563 | 612 | 'draft' => 'bg-gray-100 text-gray-800 ring-1 ring-gray-300', |
| 564 | 613 | 'overdue' => 'bg-red-100 text-red-800 ring-1 ring-red-300', |
| @@ -578,10 +627,10 @@ | ||
| 578 | 627 | // Build URL with status filter |
| 579 | 628 | $status_url = $base_url . '&status=' . urlencode($status_key); |
| 580 | 629 | ?> |
| 581 | 630 | <a href="<?php echo esc_url($status_url); ?>" |
| 582 | - class="<?php echo $base_class . ' ' . $color_class; ?>"> | |
| 583 | - <?php echo $status_label; ?> | |
| 631 | + class="<?php echo esc_html($base_class) . ' ' . esc_html($color_class); ?>"> | |
| 632 | + <?php echo esc_html($status_label); ?> | |
| 584 | 633 | </a> |
| 585 | 634 | <?php endforeach; ?> |
| 586 | 635 | </div> |
| 587 | 636 | <?php do_action('easy_invoice_inline_type_filter'); ?> |
| @@ -586,37 +635,35 @@ | ||
| 586 | 635 | </div> |
| 587 | 636 | <?php do_action('easy_invoice_inline_type_filter'); ?> |
| 588 | 637 | </div> |
| 589 | 638 | |
| 590 | - <!-- Hook for Pro plugin to add additional filters --> | |
| 591 | 639 | <?php do_action('easy_invoice_before_invoice_listing'); ?> |
| 592 | 640 | <?php endif; ?> |
| 593 | 641 | </div> |
| 594 | 642 | </div> |
| 595 | 643 | |
| 596 | - <!-- Invoices Table --> | |
| 597 | 644 | <div class="bg-white rounded-lg shadow overflow-hidden"> |
| 598 | 645 | <div class="overflow-x-auto"> |
| 599 | - <table class="min-w-full divide-y divide-gray-200"> | |
| 646 | + <table class="min-w-full divide-y divide-gray-200 ei-invoices-table"> | |
| 600 | 647 | <thead> |
| 601 | 648 | <tr class="bg-gray-50"> |
| 602 | 649 | <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-10 border-b border-gray-200"> |
| 603 | - <input type="checkbox" id="select-all" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"> | |
| 650 | + <input type="checkbox" id="select-all" aria-label="Select all invoices 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"> | |
| 604 | 651 | </th> |
| 605 | 652 | <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200"> |
| 606 | - <?php _e('Invoice', 'easy-invoice'); ?> | |
| 653 | + <?php esc_html_e('Invoice', 'easy-invoice'); ?> | |
| 607 | 654 | </th> |
| 608 | 655 | <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200"> |
| 609 | - <?php _e('Client', 'easy-invoice'); ?> | |
| 656 | + <?php esc_html_e('Client', 'easy-invoice'); ?> | |
| 610 | 657 | </th> |
| 611 | 658 | <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200"> |
| 612 | - <?php _e('Status', 'easy-invoice'); ?> | |
| 659 | + <?php esc_html_e('Status', 'easy-invoice'); ?> | |
| 613 | 660 | </th> |
| 614 | 661 | <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200"> |
| 615 | - <?php _e('Amount', 'easy-invoice'); ?> | |
| 662 | + <?php esc_html_e('Amount', 'easy-invoice'); ?> | |
| 616 | 663 | </th> |
| 617 | 664 | <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200"> |
| 618 | - <?php _e('Due Date', 'easy-invoice'); ?> | |
| 665 | + <?php esc_html_e('Due Date', 'easy-invoice'); ?> | |
| 619 | 666 | </th> |
| 620 | 667 | <?php do_action('easy_invoice_after_invoice_listing_header'); ?> |
| 621 | 668 | </tr> |
| 622 | 669 | </thead> |
| @@ -663,22 +710,20 @@ | ||
| 663 | 710 | |
| 664 | 711 | // Format the display title |
| 665 | 712 | $display_title = $invoice_title ?: 'Untitled Invoice'; |
| 666 | 713 | |
| 667 | - // Determine classes for status badge | |
| 668 | - $status_class = 'bg-gray-100 text-gray-800 ring-1 ring-gray-200'; | |
| 669 | - if ($status === 'paid') { | |
| 670 | - $status_class = 'bg-green-100 text-green-800 ring-1 ring-green-200'; | |
| 671 | - } elseif ($status === 'pending') { | |
| 672 | - $status_class = 'bg-yellow-100 text-yellow-800 ring-1 ring-yellow-200'; | |
| 673 | - } elseif ($status === 'overdue') { | |
| 674 | - $status_class = 'bg-red-100 text-red-800 ring-1 ring-red-200'; | |
| 675 | - } | |
| 714 | + // An invoice still owed after its due date reads as overdue | |
| 715 | + // whatever its stored status says (nothing flips the status | |
| 716 | + // by itself); the underlying status stays for the filters. | |
| 717 | + $status = \EasyInvoice\Helpers\InvoiceStatus::displayKey((string) $status, $due_date ? (string) $due_date : null); | |
| 718 | + $status_class = 'available' === $status | |
| 719 | + ? 'bg-gray-100 text-gray-800 ring-1 ring-gray-200' | |
| 720 | + : \EasyInvoice\Helpers\InvoiceStatus::badgeClass($status); | |
| 676 | 721 | ?> |
| 677 | 722 | <?php do_action('easy_invoice_before_invoice_listing_row', $invoice); ?> |
| 678 | - <tr class="hover:bg-gray-50 transition-colors duration-150" data-invoice-id="<?php echo $invoice_id; ?>"> | |
| 723 | + <tr class="hover:bg-gray-50 transition-colors duration-150" data-invoice-id="<?php echo (int) $invoice_id; ?>"> | |
| 679 | 724 | <td class="px-6 py-4 whitespace-nowrap"> |
| 680 | - <input type="checkbox" name="invoice_ids[]" value="<?php echo $invoice_id; ?>" form="bulk-action-form" class="invoice-checkbox rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"> | |
| 725 | + <input type="checkbox" name="invoice_ids[]" value="<?php echo (int) $invoice_id; ?>" form="bulk-action-form" aria-label="<?php /* translators: %s: invoice number. */ echo esc_attr(sprintf(__('Select invoice %s', 'easy-invoice'), $invoice_number)); ?>" class="invoice-checkbox rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"> | |
| 681 | 726 | </td> |
| 682 | 727 | <td class="px-6 py-4 whitespace-nowrap"> |
| 683 | 728 | <?php if ($current_view === 'trash'): ?> |
| 684 | 729 | <div class="text-sm font-medium text-gray-900"> |
| @@ -684,9 +729,15 @@ | ||
| 684 | 729 | <div class="text-sm font-medium text-gray-900"> |
| 685 | 730 | <?php echo esc_html($display_title); ?> |
| 686 | 731 | </div> |
| 687 | 732 | <div class="text-xs text-gray-500 mt-1"> |
| 688 | - <?php echo apply_filters('easy_invoice_invoice_number_display', esc_html($invoice_number), $invoice); ?> | |
| 733 | + <?php echo wp_kses_post(apply_filters('easy_invoice_invoice_number_display', esc_html($invoice_number), $invoice)); ?> | |
| 734 | + <?php if ('draft' !== get_post_meta($invoice_id, '_easy_invoice_status', true)) : ?> | |
| 735 | + <span class="ei-viewed ml-2 inline-flex items-center gap-1 <?php echo \EasyInvoice\Services\DocumentViews::summary($invoice_id)['count'] ? 'text-emerald-600' : 'text-gray-400'; ?>" title="<?php echo esc_attr(\EasyInvoice\Services\DocumentViews::summary($invoice_id)['from'] ? sprintf(/* translators: %s: anonymised IP. */ __('Last opened from %s', 'easy-invoice'), \EasyInvoice\Services\DocumentViews::summary($invoice_id)['from']) : ''); ?>"> | |
| 736 | + <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><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"/></svg> | |
| 737 | + <?php echo esc_html(\EasyInvoice\Services\DocumentViews::label($invoice_id)); ?> | |
| 738 | + </span> | |
| 739 | + <?php endif; ?> | |
| 689 | 740 | <?php |
| 690 | 741 | // Check if this invoice was converted from a quote |
| 691 | 742 | $quote_info = \EasyInvoice\Helpers\QuoteInvoiceHelper::getQuoteInfoFromInvoice($invoice_id); |
| 692 | 743 | if ($quote_info): ?> |
| @@ -701,19 +752,25 @@ | ||
| 701 | 752 | </span> |
| 702 | 753 | <?php endif; ?> |
| 703 | 754 | </div> |
| 704 | 755 | <div class="row-actions mt-1"> |
| 705 | - <span class="restore"><a href="#" class="restore-invoice" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"></path></svg><?php _e('Restore', 'easy-invoice'); ?></a></span> | |
| 706 | - <span class="delete"><a href="#" class="delete-permanently text-red-600" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg><?php _e('Delete Permanently', 'easy-invoice'); ?></a></span> | |
| 756 | + <span class="restore"><a href="#" class="restore-invoice" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"></path></svg><?php esc_html_e('Restore', 'easy-invoice'); ?></a></span> | |
| 757 | + <span class="delete"><a href="#" class="delete-permanently text-red-600" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg><?php esc_html_e('Delete Permanently', 'easy-invoice'); ?></a></span> | |
| 707 | 758 | </div> |
| 708 | 759 | <?php elseif ($current_view === 'draft'): ?> |
| 709 | 760 | <div class="text-sm font-medium text-gray-900"> |
| 710 | - <a href="<?php echo get_permalink($invoice_id); ?>" class="text-indigo-600 hover:text-indigo-900"> | |
| 711 | - <?php echo apply_filters('easy_invoice_invoice_title', esc_html($display_title), $invoice); ?> | |
| 761 | + <a href="<?php echo esc_url(get_permalink($invoice_id)); ?>" class="text-indigo-600 hover:text-indigo-900"> | |
| 762 | + <?php echo wp_kses_post(apply_filters('easy_invoice_invoice_title', esc_html($display_title), $invoice)); ?> | |
| 712 | 763 | </a> |
| 713 | 764 | </div> |
| 714 | 765 | <div class="text-xs text-gray-500 mt-1"> |
| 715 | - <?php echo apply_filters('easy_invoice_invoice_number_display', esc_html($invoice_number), $invoice); ?> | |
| 766 | + <?php echo wp_kses_post(apply_filters('easy_invoice_invoice_number_display', esc_html($invoice_number), $invoice)); ?> | |
| 767 | + <?php if ('draft' !== get_post_meta($invoice_id, '_easy_invoice_status', true)) : ?> | |
| 768 | + <span class="ei-viewed ml-2 inline-flex items-center gap-1 <?php echo \EasyInvoice\Services\DocumentViews::summary($invoice_id)['count'] ? 'text-emerald-600' : 'text-gray-400'; ?>" title="<?php echo esc_attr(\EasyInvoice\Services\DocumentViews::summary($invoice_id)['from'] ? sprintf(/* translators: %s: anonymised IP. */ __('Last opened from %s', 'easy-invoice'), \EasyInvoice\Services\DocumentViews::summary($invoice_id)['from']) : ''); ?>"> | |
| 769 | + <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><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"/></svg> | |
| 770 | + <?php echo esc_html(\EasyInvoice\Services\DocumentViews::label($invoice_id)); ?> | |
| 771 | + </span> | |
| 772 | + <?php endif; ?> | |
| 716 | 773 | <?php |
| 717 | 774 | // Check if this invoice was converted from a quote |
| 718 | 775 | $quote_info = \EasyInvoice\Helpers\QuoteInvoiceHelper::getQuoteInfoFromInvoice($invoice_id); |
| 719 | 776 | if ($quote_info): ?> |
| @@ -728,18 +785,18 @@ | ||
| 728 | 785 | </span> |
| 729 | 786 | <?php endif; ?> |
| 730 | 787 | </div> |
| 731 | 788 | <div class="row-actions mt-1"> |
| 732 | - <span class="edit"><a href="<?php echo admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoice_id); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg><?php _e('Edit', 'easy-invoice'); ?></a></span> | |
| 733 | - <span class="publish"><a href="#" class="publish-invoice" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg><?php _e('Publish', 'easy-invoice'); ?></a></span> | |
| 734 | - <span class="trash"><a href="#" class="trash-invoice text-red-600" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg><?php _e('Trash', 'easy-invoice'); ?></a></span> | |
| 735 | - <span class="email"><a href="#" class="email-invoice" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg><?php _e('Send Email', 'easy-invoice'); ?></a></span> | |
| 736 | - <span class="pdf"><a href="<?php echo get_permalink($invoice_id); ?>?auto_download_pdf=1" target="_blank" class="download-pdf" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg><?php _e('Download PDF', 'easy-invoice'); ?></a></span> | |
| 789 | + <span class="edit"><a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoice_id)); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg><?php esc_html_e('Edit', 'easy-invoice'); ?></a></span> | |
| 790 | + <span class="publish"><a href="#" class="publish-invoice" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg><?php esc_html_e('Publish', 'easy-invoice'); ?></a></span> | |
| 791 | + <span class="trash"><a href="#" class="trash-invoice text-red-600" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg><?php esc_html_e('Trash', 'easy-invoice'); ?></a></span> | |
| 792 | + <span class="email"><a href="#" class="email-invoice" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg><?php esc_html_e('Send Email', 'easy-invoice'); ?></a></span> | |
| 793 | + <span class="pdf"><a href="<?php echo esc_url(get_permalink($invoice_id)); ?>?auto_download_pdf=1" target="_blank" class="download-pdf" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg><?php esc_html_e('Download PDF', 'easy-invoice'); ?></a></span> | |
| 737 | 794 | <?php if (!easy_invoice_has_pro()): ?> |
| 738 | - <span class="export premium-feature"><a href="#" class="export-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg>Export</a></span> | |
| 795 | + <span class="export premium-feature"><a href="#" class="export-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg>Export</a></span> | |
| 739 | 796 | <?php endif; ?> |
| 740 | 797 | <?php if (!easy_invoice_has_pro()): ?> |
| 741 | - <span class="duplicate premium-feature"><a href="#" class="duplicate-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 7l2 4 4 .5-3 2.5.5 4-3.5-2-3.5 2 .5-4-3-2.5 4-.5 2-4z"/></svg>Duplicate</a></span> | |
| 798 | + <span class="duplicate premium-feature"><a href="#" class="duplicate-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 7l2 4 4 .5-3 2.5.5 4-3.5-2-3.5 2 .5-4-3-2.5 4-.5 2-4z"/></svg>Duplicate</a></span> | |
| 742 | 799 | <?php endif; ?> |
| 743 | 800 | |
| 744 | 801 | <?php |
| 745 | 802 | // Apply filter for Pro plugin to add secure link actions |
| @@ -744,20 +801,26 @@ | ||
| 744 | 801 | <?php |
| 745 | 802 | // Apply filter for Pro plugin to add secure link actions |
| 746 | 803 | $secure_actions = apply_filters('easy_invoice_invoice_row_actions', [], $invoice); |
| 747 | 804 | foreach ($secure_actions as $action_key => $action_html) { |
| 748 | - echo '<span class="' . esc_attr($action_key) . '">' . $action_html . '</span>'; | |
| 805 | + echo '<span class="' . esc_attr($action_key) . '">' . wp_kses_post($action_html) . '</span>'; | |
| 749 | 806 | } |
| 750 | 807 | ?> |
| 751 | 808 | </div> |
| 752 | 809 | <?php else: ?> |
| 753 | 810 | <div class="text-sm font-medium text-gray-900"> |
| 754 | - <a href="<?php echo admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoice_id); ?>" class="text-indigo-600 hover:text-indigo-900"> | |
| 755 | - <?php echo apply_filters('easy_invoice_invoice_title', esc_html($display_title), $invoice); ?> | |
| 811 | + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoice_id)); ?>" class="text-indigo-600 hover:text-indigo-900"> | |
| 812 | + <?php echo wp_kses_post(apply_filters('easy_invoice_invoice_title', esc_html($display_title), $invoice)); ?> | |
| 756 | 813 | </a> |
| 757 | 814 | </div> |
| 758 | 815 | <div class="text-xs text-gray-500 mt-1"> |
| 759 | - <?php echo apply_filters('easy_invoice_invoice_number_display', esc_html($invoice_number), $invoice); ?> | |
| 816 | + <?php echo wp_kses_post(apply_filters('easy_invoice_invoice_number_display', esc_html($invoice_number), $invoice)); ?> | |
| 817 | + <?php if ('draft' !== get_post_meta($invoice_id, '_easy_invoice_status', true)) : ?> | |
| 818 | + <span class="ei-viewed ml-2 inline-flex items-center gap-1 <?php echo \EasyInvoice\Services\DocumentViews::summary($invoice_id)['count'] ? 'text-emerald-600' : 'text-gray-400'; ?>" title="<?php echo esc_attr(\EasyInvoice\Services\DocumentViews::summary($invoice_id)['from'] ? sprintf(/* translators: %s: anonymised IP. */ __('Last opened from %s', 'easy-invoice'), \EasyInvoice\Services\DocumentViews::summary($invoice_id)['from']) : ''); ?>"> | |
| 819 | + <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><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"/></svg> | |
| 820 | + <?php echo esc_html(\EasyInvoice\Services\DocumentViews::label($invoice_id)); ?> | |
| 821 | + </span> | |
| 822 | + <?php endif; ?> | |
| 760 | 823 | <?php |
| 761 | 824 | // Check if this invoice was converted from a quote |
| 762 | 825 | $quote_info = \EasyInvoice\Helpers\QuoteInvoiceHelper::getQuoteInfoFromInvoice($invoice_id); |
| 763 | 826 | if ($quote_info): ?> |
| @@ -772,19 +835,23 @@ | ||
| 772 | 835 | </span> |
| 773 | 836 | <?php endif; ?> |
| 774 | 837 | </div> |
| 775 | 838 | <div class="row-actions mt-1"> |
| 776 | - <span class="view"><a href="<?php echo apply_filters('easy_invoice_invoice_view_url', get_permalink($invoice_id), $invoice_id); ?>" target="_blank"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><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></svg><?php _e('View', 'easy-invoice'); ?></a></span> | |
| 777 | - <span class="edit"><a href="<?php echo admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoice_id); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg><?php _e('Edit', 'easy-invoice'); ?></a></span> | |
| 778 | - <span class="draft"><a href="#" class="draft-invoice" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg><?php _e('Draft', 'easy-invoice'); ?></a></span> | |
| 779 | - <span class="trash"><a href="#" class="trash-invoice text-red-600" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg><?php _e('Trash', 'easy-invoice'); ?></a></span> | |
| 780 | - <span class="email"><a href="#" class="email-invoice" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg><?php _e('Send Email', 'easy-invoice'); ?></a></span> | |
| 781 | - <span class="pdf"><a href="<?php echo get_permalink($invoice_id); ?>?auto_download_pdf=1" target="_blank" class="download-pdf" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg><?php _e('Download PDF', 'easy-invoice'); ?></a></span> | |
| 839 | + <span class="view"><a href="<?php echo esc_url(apply_filters('easy_invoice_invoice_view_url', get_permalink($invoice_id), $invoice_id)); ?>" target="_blank"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><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></svg><?php esc_html_e('View', 'easy-invoice'); ?></a></span> | |
| 840 | + <span class="edit"><a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $invoice_id)); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg><?php esc_html_e('Edit', 'easy-invoice'); ?></a></span> | |
| 841 | + <?php if (in_array((string) $invoice->getStatus(), ['available', 'unpaid', 'overdue'], true)) : // paid, part-paid and cancelled invoices are not un-issued ?> | |
| 842 | + <?php if (is_object($invoice) && in_array(strtolower((string) $invoice->getStatus()), ['available', 'unpaid', 'overdue'], true)) : // paid, part-paid and cancelled invoices are not un-issued ?> | |
| 843 | + <span class="draft"><a href="#" class="draft-invoice" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg><?php esc_html_e('Draft', 'easy-invoice'); ?></a></span> | |
| 844 | + <?php endif; ?> | |
| 845 | + <?php endif; ?> | |
| 846 | + <span class="trash"><a href="#" class="trash-invoice text-red-600" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg><?php esc_html_e('Trash', 'easy-invoice'); ?></a></span> | |
| 847 | + <span class="email"><a href="#" class="email-invoice" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg><?php esc_html_e('Send Email', 'easy-invoice'); ?></a></span> | |
| 848 | + <span class="pdf"><a href="<?php echo esc_url(get_permalink($invoice_id)); ?>?auto_download_pdf=1" target="_blank" class="download-pdf" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg><?php esc_html_e('Download PDF', 'easy-invoice'); ?></a></span> | |
| 782 | 849 | <?php if (!easy_invoice_has_pro()): ?> |
| 783 | - <span class="export premium-feature"><a href="#" class="export-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg>Export</a></span> | |
| 850 | + <span class="export premium-feature"><a href="#" class="export-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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></svg>Export</a></span> | |
| 784 | 851 | <?php endif; ?> |
| 785 | 852 | <?php if (!easy_invoice_has_pro()): ?> |
| 786 | - <span class="duplicate premium-feature"><a href="#" class="duplicate-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 7l2 4 4 .5-3 2.5.5 4-3.5-2-3.5 2 .5-4-3-2.5 4-.5 2-4z"/></svg>Duplicate</a></span> | |
| 853 | + <span class="duplicate premium-feature"><a href="#" class="duplicate-invoice-pro-placeholder text-yellow-600 font-semibold" data-invoice-id="<?php echo (int) $invoice_id; ?>" data-invoice-number="<?php echo esc_attr($invoice_number); ?>"><svg class="w-3.5 h-3.5 mr-1 inline-block text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 7l2 4 4 .5-3 2.5.5 4-3.5-2-3.5 2 .5-4-3-2.5 4-.5 2-4z"/></svg>Duplicate</a></span> | |
| 787 | 854 | <?php endif; ?> |
| 788 | 855 | |
| 789 | 856 | <?php |
| 790 | 857 | // Apply filter for Pro plugin to add secure link actions |
| @@ -789,9 +856,9 @@ | ||
| 789 | 856 | <?php |
| 790 | 857 | // Apply filter for Pro plugin to add secure link actions |
| 791 | 858 | $secure_actions = apply_filters('easy_invoice_invoice_row_actions', [], $invoice); |
| 792 | 859 | foreach ($secure_actions as $action_key => $action_html) { |
| 793 | - echo '<span class="' . esc_attr($action_key) . '">' . $action_html . '</span>'; | |
| 860 | + echo '<span class="' . esc_attr($action_key) . '">' . wp_kses_post($action_html) . '</span>'; | |
| 794 | 861 | } |
| 795 | 862 | ?> |
| 796 | 863 | </div> |
| 797 | 864 | <?php endif; ?> |
| @@ -800,10 +867,10 @@ | ||
| 800 | 867 | <div class="text-sm text-gray-900"><?php echo esc_html($client_name ?: 'No Client'); ?></div> |
| 801 | 868 | <div class="text-sm text-gray-500"><?php echo esc_html($client_email ?: ''); ?></div> |
| 802 | 869 | </td> |
| 803 | 870 | <td class="px-6 py-4 whitespace-nowrap"> |
| 804 | - <span class="px-3 py-1 inline-flex text-xs leading-5 font-medium rounded-full <?php echo $status_class; ?>"> | |
| 805 | - <?php echo ucfirst(esc_html($status)); ?> | |
| 871 | + <span class="px-3 py-1 inline-flex text-xs leading-5 font-medium rounded-full <?php echo esc_attr($status_class); ?>"> | |
| 872 | + <?php echo esc_html(\EasyInvoice\Helpers\InvoiceStatus::label($status)); ?> | |
| 806 | 873 | </span> |
| 807 | 874 | </td> |
| 808 | 875 | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700 font-medium"> |
| 809 | 876 | <?php echo esc_html($amount); ?> |
| @@ -817,14 +884,28 @@ | ||
| 817 | 884 | |
| 818 | 885 | <?php if (empty($invoices)): ?> |
| 819 | 886 | <tr> |
| 820 | 887 | <td colspan="7" class="px-6 py-4 text-center text-sm text-gray-500"> |
| 888 | + <?php | |
| 889 | + // These three strings were hardcoded English with no | |
| 890 | + // __() wrapper, so they could never be translated even | |
| 891 | + // though the rest of the screen is fully localised. | |
| 892 | + $ei_create_link = '<a href="#" class="text-indigo-600 hover:text-indigo-900 create-first-invoice">' | |
| 893 | + . esc_html__('Create your first invoice', 'easy-invoice') | |
| 894 | + . '</a>'; | |
| 895 | + ?> | |
| 821 | 896 | <?php if ($current_view === 'trash'): ?> |
| 822 | - No invoices found in trash. | |
| 897 | + <?php esc_html_e('No invoices found in trash.', 'easy-invoice'); ?> | |
| 823 | 898 | <?php elseif ($current_view === 'draft'): ?> |
| 824 | - No invoices found in draft. <a href="#" class="text-indigo-600 hover:text-indigo-900 create-first-invoice">Create your first invoice</a>. | |
| 899 | + <?php | |
| 900 | + /* translators: %s: link reading "Create your first invoice". */ | |
| 901 | + printf(esc_html__('No invoices found in draft. %s.', 'easy-invoice'), wp_kses_post($ei_create_link)); | |
| 902 | + ?> | |
| 825 | 903 | <?php else: ?> |
| 826 | - No invoices found. <a href="#" class="text-indigo-600 hover:text-indigo-900 create-first-invoice">Create your first invoice</a>. | |
| 904 | + <?php | |
| 905 | + /* translators: %s: link reading "Create your first invoice". */ | |
| 906 | + printf(esc_html__('No invoices found. %s.', 'easy-invoice'), wp_kses_post($ei_create_link)); | |
| 907 | + ?> | |
| 827 | 908 | <?php endif; ?> |
| 828 | 909 | </td> |
| 829 | 910 | </tr> |
| 830 | 911 | <?php endif; ?> |
| @@ -832,9 +913,8 @@ | ||
| 832 | 913 | </table> |
| 833 | 914 | </div> |
| 834 | 915 | </div> |
| 835 | 916 | |
| 836 | - <!-- Pagination --> | |
| 837 | 917 | <?php if ($total_invoices > 0): ?> |
| 838 | 918 | <div class="mt-4"> |
| 839 | 919 | <div class="px-4 py-3 flex items-center justify-between"> |
| 840 | 920 | <div class="flex items-center space-x-2"> |
| @@ -891,9 +971,9 @@ | ||
| 891 | 971 | if ($link && strpos($link, 'bg-indigo-50') === false && strpos($link, 'bg-white') === false) { |
| 892 | 972 | $link = easy_invoice_str_replace('border-gray-300', 'border-gray-300 bg-white text-gray-500 hover:bg-gray-50', $link); |
| 893 | 973 | } |
| 894 | 974 | |
| 895 | - echo $link; | |
| 975 | + echo wp_kses_post($link); | |
| 896 | 976 | } |
| 897 | 977 | echo '</div>'; |
| 898 | 978 | } |
| 899 | 979 | ?> |
| @@ -903,15 +983,14 @@ | ||
| 903 | 983 | </div> |
| 904 | 984 | <?php endif; ?> |
| 905 | 985 | </div> |
| 906 | 986 | |
| 907 | -<!-- Delete Invoice Confirmation Modal --> | |
| 908 | 987 | <div id="trash-invoice-modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50"> |
| 909 | 988 | <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6 relative"> |
| 910 | 989 | <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500"> |
| 911 | 990 | <i class="fas fa-times"></i> |
| 912 | 991 | </button> |
| 913 | - <h2 class="text-xl font-medium text-gray-900 mb-4">Move to Trash</h2> | |
| 992 | + <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Move to Trash', 'easy-invoice'); ?></h2> | |
| 914 | 993 | <p class="text-sm text-gray-500 mb-4">Are you sure you want to move invoice <span id="trash-invoice-number" class="font-medium"></span> to trash?</p> |
| 915 | 994 | |
| 916 | 995 | <div class="mt-5 flex justify-end space-x-3"> |
| 917 | 996 | <button type="button" class="cancel-modal bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> |
| @@ -923,15 +1002,14 @@ | ||
| 923 | 1002 | </div> |
| 924 | 1003 | </div> |
| 925 | 1004 | </div> |
| 926 | 1005 | |
| 927 | -<!-- Restore Invoice Confirmation Modal --> | |
| 928 | 1006 | <div id="restore-invoice-modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50"> |
| 929 | 1007 | <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6 relative"> |
| 930 | 1008 | <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500"> |
| 931 | 1009 | <i class="fas fa-times"></i> |
| 932 | 1010 | </button> |
| 933 | - <h2 class="text-xl font-medium text-gray-900 mb-4">Restore Invoice</h2> | |
| 1011 | + <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Restore Invoice', 'easy-invoice'); ?></h2> | |
| 934 | 1012 | <p class="text-sm text-gray-500 mb-4">Are you sure you want to restore invoice <span id="restore-invoice-number" class="font-medium"></span>?</p> |
| 935 | 1013 | |
| 936 | 1014 | <div class="mt-5 flex justify-end space-x-3"> |
| 937 | 1015 | <button type="button" class="cancel-modal bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> |
| @@ -943,15 +1021,14 @@ | ||
| 943 | 1021 | </div> |
| 944 | 1022 | </div> |
| 945 | 1023 | </div> |
| 946 | 1024 | |
| 947 | -<!-- Permanently Delete Invoice Confirmation Modal --> | |
| 948 | 1025 | <div id="delete-permanently-modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50"> |
| 949 | 1026 | <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6 relative"> |
| 950 | 1027 | <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500"> |
| 951 | 1028 | <i class="fas fa-times"></i> |
| 952 | 1029 | </button> |
| 953 | - <h2 class="text-xl font-medium text-gray-900 mb-4">Delete Invoice Permanently</h2> | |
| 1030 | + <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Delete Invoice Permanently', 'easy-invoice'); ?></h2> | |
| 954 | 1031 | <p class="text-sm text-gray-500 mb-4">Are you sure you want to permanently delete invoice <span id="delete-permanently-number" class="font-medium"></span>? This action cannot be undone.</p> |
| 955 | 1032 | |
| 956 | 1033 | <div class="mt-5 flex justify-end space-x-3"> |
| 957 | 1034 | <button type="button" class="cancel-modal bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> |
| @@ -963,15 +1040,14 @@ | ||
| 963 | 1040 | </div> |
| 964 | 1041 | </div> |
| 965 | 1042 | </div> |
| 966 | 1043 | |
| 967 | -<!-- Publish Invoice Confirmation Modal --> | |
| 968 | 1044 | <div id="publish-invoice-modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50"> |
| 969 | 1045 | <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6 relative"> |
| 970 | 1046 | <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500"> |
| 971 | 1047 | <i class="fas fa-times"></i> |
| 972 | 1048 | </button> |
| 973 | - <h2 class="text-xl font-medium text-gray-900 mb-4">Publish Invoice</h2> | |
| 1049 | + <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Publish Invoice', 'easy-invoice'); ?></h2> | |
| 974 | 1050 | <p class="text-sm text-gray-500 mb-4">Are you sure you want to publish invoice <span id="publish-invoice-number" class="font-medium"></span>?</p> |
| 975 | 1051 | |
| 976 | 1052 | <div class="mt-5 flex justify-end space-x-3"> |
| 977 | 1053 | <button type="button" class="cancel-modal bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> |
| @@ -983,16 +1059,15 @@ | ||
| 983 | 1059 | </div> |
| 984 | 1060 | </div> |
| 985 | 1061 | </div> |
| 986 | 1062 | |
| 987 | -<!-- Draft Invoice Confirmation Modal --> | |
| 988 | 1063 | <div id="draft-invoice-modal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center hidden z-50"> |
| 989 | 1064 | <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6 relative"> |
| 990 | 1065 | <button class="close-modal absolute top-4 right-4 text-gray-400 hover:text-gray-500"> |
| 991 | 1066 | <i class="fas fa-times"></i> |
| 992 | 1067 | </button> |
| 993 | - <h2 class="text-xl font-medium text-gray-900 mb-4">Set Invoice to Draft</h2> | |
| 994 | - <p class="text-sm text-gray-500 mb-4">Are you sure you want to set invoice <span id="draft-invoice-number" class="font-medium"></span> to draft status?</p> | |
| 1068 | + <h2 class="text-xl font-medium text-gray-900 mb-4"><?php esc_html_e('Set Invoice to Draft', 'easy-invoice'); ?></h2> | |
| 1069 | + <p class="text-sm text-gray-500 mb-4"><?php esc_html_e('Set invoice', 'easy-invoice'); ?> <span id="draft-invoice-number" class="font-medium"></span> <?php esc_html_e('back to draft? It stays in your list and keeps its number, but reads as not yet issued until you send it again.', 'easy-invoice'); ?></p> | |
| 995 | 1070 | |
| 996 | 1071 | <div class="mt-5 flex justify-end space-x-3"> |
| 997 | 1072 | <button type="button" class="cancel-modal bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> |
| 998 | 1073 | Cancel |
| @@ -1005,20 +1080,48 @@ | ||
| 1005 | 1080 | </div> |
| 1006 | 1081 | |
| 1007 | 1082 | <style> |
| 1008 | 1083 | /* Modern row actions styling */ |
| 1084 | +/* Team roles: controls a member may not use are not drawn (the endpoints check again). */ | |
| 1085 | +<?php if (!easy_invoice_user_can('ei_create_invoice')) : ?>.row-actions .edit, .row-actions .publish, .row-actions .draft { display: none; }<?php endif; ?> | |
| 1086 | +<?php if (!easy_invoice_user_can('ei_delete_invoice')) : ?>.row-actions .trash, .row-actions .delete, .row-actions .restore { display: none; }<?php endif; ?> | |
| 1087 | +<?php if (!easy_invoice_user_can('ei_send_invoice')) : ?>.row-actions .email { display: none; }<?php endif; ?> | |
| 1088 | +.ei-invoices-table td:nth-child(2) { white-space: normal; } | |
| 1089 | +.ei-invoices-table td:nth-child(2) .ei-viewed { white-space: nowrap; } | |
| 1009 | 1090 | .row-actions { |
| 1010 | 1091 | visibility: hidden; |
| 1011 | 1092 | font-size: 12px; |
| 1012 | 1093 | padding-top: 4px; |
| 1013 | 1094 | display: flex; |
| 1014 | - gap: 8px; | |
| 1095 | + flex-wrap: wrap; /* a long action list must not widen the column past the screen */ | |
| 1096 | + gap: 4px 8px; | |
| 1015 | 1097 | } |
| 1016 | 1098 | |
| 1017 | -tr:hover .row-actions { | |
| 1099 | +tr:hover .row-actions, | |
| 1100 | +tr:focus-within .row-actions { | |
| 1018 | 1101 | visibility: visible; |
| 1019 | 1102 | } |
| 1020 | 1103 | |
| 1104 | +/* | |
| 1105 | + * Row actions were revealed on `tr:hover` only, which made them unreachable for | |
| 1106 | + * two whole groups of users: | |
| 1107 | + * | |
| 1108 | + * - Keyboard users. `visibility: hidden` also removes elements from the tab | |
| 1109 | + * order, so View / Edit / Trash / Send Email / Download PDF / Duplicate could | |
| 1110 | + * never be focused at all. `:focus-within` above fixes that — tabbing into the | |
| 1111 | + * row now reveals them. | |
| 1112 | + * - Touch users. There is no hover on a phone or tablet, so every row action was | |
| 1113 | + * permanently invisible and there was no other way to reach them. | |
| 1114 | + * | |
| 1115 | + * On pointer-less devices, and on the narrow widths WordPress itself treats as | |
| 1116 | + * mobile admin, the actions are simply always visible. | |
| 1117 | + */ | |
| 1118 | +@media (hover: none), (max-width: 782px) { | |
| 1119 | + .row-actions { | |
| 1120 | + visibility: visible; | |
| 1121 | + } | |
| 1122 | +} | |
| 1123 | + | |
| 1021 | 1124 | .row-actions span a { |
| 1022 | 1125 | color: #6366f1; /* indigo-500 */ |
| 1023 | 1126 | text-decoration: none; |
| 1024 | 1127 | transition: all 0.2s; |
| @@ -1277,46 +1380,30 @@ | ||
| 1277 | 1380 | } |
| 1278 | 1381 | } |
| 1279 | 1382 | }); |
| 1280 | 1383 | |
| 1281 | - // Handle Download PDF click | |
| 1282 | - $(".download-pdf").on("click", function(e) { | |
| 1283 | - e.preventDefault(); | |
| 1284 | - var invoiceId = $(this).data("invoice-id"); | |
| 1285 | - var invoiceNumber = $(this).data("invoice-number"); | |
| 1286 | - | |
| 1287 | - // Show loading state | |
| 1288 | - var $link = $(this); | |
| 1289 | - var originalText = $link.text(); | |
| 1290 | - $link.text("Generating PDF...").css("opacity", "0.7"); | |
| 1291 | - | |
| 1292 | - // Get invoice data via AJAX and generate PDF | |
| 1293 | - $.ajax({ | |
| 1294 | - url: easyInvoice.ajaxUrl, | |
| 1295 | - type: "POST", | |
| 1296 | - data: { | |
| 1297 | - action: "easy_invoice_download_invoice_pdf", | |
| 1298 | - invoice_id: invoiceId, | |
| 1299 | - nonce: easyInvoice.nonce | |
| 1300 | - }, | |
| 1301 | - success: function(response) { | |
| 1302 | - if (response.success && response.data.download_url) { | |
| 1303 | - // Open the download URL in a new tab | |
| 1304 | - window.open(response.data.download_url, '_blank'); | |
| 1305 | - EasyInvoiceToast.success("PDF download started"); | |
| 1306 | - } else { | |
| 1307 | - EasyInvoiceToast.error(response.data.message || "Error preparing PDF data"); | |
| 1308 | - } | |
| 1309 | - // Reset link text | |
| 1310 | - $link.text(originalText).css("opacity", "1"); | |
| 1311 | - }, | |
| 1312 | - error: function() { | |
| 1313 | - EasyInvoiceToast.error("Error connecting to server"); | |
| 1314 | - // Reset link text | |
| 1315 | - $link.text(originalText).css("opacity", "1"); | |
| 1316 | - } | |
| 1317 | - }); | |
| 1318 | - }); | |
| 1384 | + // Download PDF: let the anchor's native href do the job. | |
| 1385 | + // | |
| 1386 | + // Historically this handler intercepted the click and did an | |
| 1387 | + // admin-ajax round-trip that minted a per-user nonce and opened | |
| 1388 | + // the resulting admin-ajax URL in a new tab. That extra hop was | |
| 1389 | + // fragile in production — page-cache layers, security plugins / | |
| 1390 | + // WAFs, cross-tab session cookie drops (Safari ITP, SameSite), | |
| 1391 | + // and PHP output-buffering interactions all caused the target | |
| 1392 | + // handler's redirect to be swallowed, leaving the user on a | |
| 1393 | + // blank admin-ajax page with no download. | |
| 1394 | + // | |
| 1395 | + // The anchor's href is already `<permalink>?auto_download_pdf=1` | |
| 1396 | + // (see line ~756). The single-invoice page JS picks up that | |
| 1397 | + // query arg and triggers the PDF render directly. Removing the | |
| 1398 | + // interceptor collapses three server round-trips into one and | |
| 1399 | + // sidesteps every failure mode above. The | |
| 1400 | + // `easy_invoice_download_invoice_pdf` / | |
| 1401 | + // `easy_invoice_generate_pdf` handlers are still registered on | |
| 1402 | + // the server for any external integration that used them (they | |
| 1403 | + // now have their own robust 3-path auth + JS redirect fallback | |
| 1404 | + // — see EasyInvoiceAjax::generateInvoicePdf), but this button | |
| 1405 | + // no longer depends on them. | |
| 1319 | 1406 | |
| 1320 | 1407 | // Close modals when Close button or Cancel is clicked |
| 1321 | 1408 | $(".close-modal, .cancel-modal").on("click", function() { |
| 1322 | 1409 | $(".fixed").addClass("hidden"); |
| @@ -1526,42 +1613,11 @@ | ||
| 1526 | 1613 | e.preventDefault(); |
| 1527 | 1614 | EasyInvoiceConfirmation.showFeatureUpgrade('Invoice Export', 'Export your invoices to CSV, Excel, or PDF format for backup, analysis, or sharing with your team.'); |
| 1528 | 1615 | }); |
| 1529 | 1616 | |
| 1530 | - // Handle "Create Sample Invoice" button click | |
| 1531 | - $('#easy-invoice-create-sample').on('click', function() { | |
| 1532 | - var $button = $(this); | |
| 1533 | - var original_text = $button.html(); | |
| 1534 | - $button.html('Creating...').prop('disabled', true); | |
| 1535 | - | |
| 1536 | - $.ajax({ | |
| 1537 | - url: ajaxurl, // WordPress AJAX URL | |
| 1538 | - type: 'POST', | |
| 1539 | - data: { | |
| 1540 | - action: 'easy_invoice_create_sample_invoice', | |
| 1541 | - nonce: '<?php echo wp_create_nonce("easy_invoice_admin_nonce"); ?>' // Make sure this nonce matches the one checked in PHP | |
| 1542 | - }, | |
| 1543 | - success: function(response) { | |
| 1544 | - if (response.success) { | |
| 1545 | - EasyInvoiceToast.success(response.data.message + ' You can edit it at: ' + response.data.edit_link); | |
| 1546 | - // Optionally, refresh the page or redirect | |
| 1547 | - window.location.reload(); | |
| 1548 | - } else { | |
| 1549 | - EasyInvoiceToast.error('Error: ' + response.data.message); | |
| 1550 | - } | |
| 1551 | - }, | |
| 1552 | - error: function(jqXHR, textStatus, errorThrown) { | |
| 1553 | - EasyInvoiceToast.error('AJAX Error: ' + textStatus + ' - ' + errorThrown); | |
| 1554 | - }, | |
| 1555 | - complete: function() { | |
| 1556 | - $button.html(original_text).prop('disabled', false); | |
| 1557 | - } | |
| 1558 | - }); | |
| 1559 | - }); | |
| 1560 | 1617 | }); |
| 1561 | 1618 | </script> |
| 1562 | 1619 | |
| 1563 | -<!-- New Invoice Modal --> | |
| 1564 | 1620 | <div id="new-invoice-modal" class="fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50 hidden"> |
| 1565 | 1621 | <div class="relative mx-auto p-6 border w-96 shadow-xl rounded-lg bg-white my-20"> |
| 1566 | 1622 | <div class="flex justify-between items-center mb-6"> |
| 1567 | 1623 | <h3 class="text-xl font-semibold text-gray-900"><?php esc_html_e('Create New Invoice', 'easy-invoice'); ?></h3> |
| @@ -1580,9 +1636,9 @@ | ||
| 1580 | 1636 | id="new_invoice_title" |
| 1581 | 1637 | required |
| 1582 | 1638 | placeholder="Enter invoice title..." |
| 1583 | 1639 | class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-3 px-4 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm transition-colors duration-200"> |
| 1584 | - <p class="mt-2 text-sm text-gray-500">This will be the title of your invoice</p> | |
| 1640 | + <p class="mt-2 text-sm text-gray-500"><?php esc_html_e('This will be the title of your invoice', 'easy-invoice'); ?></p> | |
| 1585 | 1641 | </div> |
| 1586 | 1642 | |
| 1587 | 1643 | <div class="flex justify-end space-x-3 pt-4"> |
| 1588 | 1644 | <button type="button" id="cancel-new-invoice" |
| @@ -1603,10 +1659,10 @@ | ||
| 1603 | 1659 | <script> |
| 1604 | 1660 | jQuery(document).ready(function($) { |
| 1605 | 1661 | // Initialize easyInvoice object for AJAX calls |
| 1606 | 1662 | window.easyInvoice = { |
| 1607 | - ajaxUrl: '<?php echo admin_url('admin-ajax.php'); ?>', | |
| 1608 | - nonce: '<?php echo wp_create_nonce('easy_invoice_nonce'); ?>', | |
| 1663 | + ajaxUrl: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>', | |
| 1664 | + nonce: '<?php echo esc_attr(wp_create_nonce('easy_invoice_nonce')); ?>', | |
| 1609 | 1665 | isPro: <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?> |
| 1610 | 1666 | }; |
| 1611 | 1667 | |
| 1612 | 1668 | // New Invoice Modal Functionality |
| @@ -1647,9 +1703,9 @@ | ||
| 1647 | 1703 | var $message = $('#new-invoice-message'); |
| 1648 | 1704 | var title = $('#new_invoice_title').val().trim(); |
| 1649 | 1705 | |
| 1650 | 1706 | if (!title) { |
| 1651 | - $message.removeClass('hidden').html('<div class="text-red-600 text-sm">Please enter an invoice title.</div>'); | |
| 1707 | + $message.removeClass('hidden').html('<div class="text-red-600 text-sm"><?php esc_html_e('Please enter an invoice title.', 'easy-invoice'); ?></div>'); | |
| 1652 | 1708 | return; |
| 1653 | 1709 | } |
| 1654 | 1710 | |
| 1655 | 1711 | // Show loading state |
| @@ -1666,9 +1722,9 @@ | ||
| 1666 | 1722 | }, |
| 1667 | 1723 | success: function(response) { |
| 1668 | 1724 | if (response.success) { |
| 1669 | 1725 | // Show success message |
| 1670 | - $message.removeClass('hidden').html('<div class="text-green-600 text-sm">Invoice created successfully! Redirecting...</div>'); | |
| 1726 | + $message.removeClass('hidden').html('<div class="text-green-600 text-sm"><?php esc_html_e('Invoice created successfully! Redirecting...', 'easy-invoice'); ?></div>'); | |
| 1671 | 1727 | |
| 1672 | 1728 | // Redirect to the builder page with the new invoice ID |
| 1673 | 1729 | setTimeout(function() { |
| 1674 | 1730 | window.location.href = response.data.redirect_url; |
| @@ -1678,9 +1734,9 @@ | ||
| 1678 | 1734 | $submitBtn.prop('disabled', false).html('Create Invoice'); |
| 1679 | 1735 | } |
| 1680 | 1736 | }, |
| 1681 | 1737 | error: function() { |
| 1682 | - $message.removeClass('hidden').html('<div class="text-red-600 text-sm">Error connecting to server. Please try again.</div>'); | |
| 1738 | + $message.removeClass('hidden').html('<div class="text-red-600 text-sm"><?php esc_html_e('Error connecting to server. Please try again.', 'easy-invoice'); ?></div>'); | |
| 1683 | 1739 | $submitBtn.prop('disabled', false).html('Create Invoice'); |
| 1684 | 1740 | } |
| 1685 | 1741 | }); |
| 1686 | 1742 | }); |