PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.2
2.4.3 2.4.4 2.4.2 2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 All 60 releases
← All changes | templates/invoices/listing.php +289 -233 2.1.21 → 2.4.2 View file →
@@ -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 });