PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.0.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.0.0
2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
← All changes | includes/Admin/views/subscription-list.php +104 -161 1.10.4 → 2.0.0 View file →
@@ -11,33 +11,43 @@
11 11
12 12 if ( ! isset( $date_filter ) ) {
13 13 $date_filter = '';
14 14 }
15 +if ( ! isset( $renewal_due ) ) {
16 + $renewal_due = 0;
17 +}
15 18
16 -$filters_active = ! empty( $status ) || ! empty( $date_filter ) || ! empty( $search );
17 -$months = array();
19 +$filters_active = ! empty( $status ) || ! empty( $date_filter ) || ! empty( $search ) || ! empty( $renewal_due );
20 +
21 +// The last twelve of the store's months, counted back from the 1st: stepping
22 +// back from today skips a month on the 29th–31st (31 Oct − 1 month is 1 Oct).
23 +// The store's timezone, because the list filters on the local post date.
24 +$months = array();
25 +$this_month = ( new DateTimeImmutable( 'now', wp_timezone() ) )->modify( 'first day of this month' )->setTime( 0, 0 );
18 26 for ( $i = 0; $i < 12; $i++ ) {
19 27 $month = strtotime( "-$i month" );
20 - $months[ date( 'Y-m', $month ) ] = date( 'F Y', $month );
28 + $months[ gmdate( 'Y-m', $month ) ] = gmdate( 'F Y', $month );
21 29 }
22 30 ?>
23 -<div class="wp-subscription-admin-content list-page subscrpt-subs-list">
31 +<div class="wp-subscription-admin-content list-page">
24 32
25 33 <?php
26 - // Getting started card — hidden once a subscription product exists.
27 - require __DIR__ . '/subscription-gsc.php';
34 + // Getting started ("Welcome to WPSubscription") card hidden for now — re-enable later.
35 + // require __DIR__ . '/subscription-gsc.php';
28 36 ?>
29 37
30 - <!-- Page header -->
31 - <div style="margin-bottom:20px;">
32 - <h1 style="font-size:1.375rem;font-weight:700;color:var(--wpsubs-text);margin:0 0 6px;line-height:1.2;"><?php esc_html_e( 'Subscriptions', 'subscription' ); ?></h1>
33 - <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0 0 12px;line-height:1.5;"><?php esc_html_e( 'Manage all your WooCommerce subscriptions.', 'subscription' ); ?></p>
34 - <div style="border-top:1px dashed #d0d3d7;"></div>
35 - </div>
38 + <?php
39 + wpsubs_render_page_header(
40 + array(
41 + 'title' => __( 'Subscriptions', 'subscription' ),
42 + 'description' => __( 'Manage all your WooCommerce subscriptions.', 'subscription' ),
43 + )
44 + );
45 + ?>
36 46
37 47 <form method="post" id="subscriptions-form">
38 48 <?php wp_nonce_field( 'subscrpt_list_action' ); ?>
39 - <input type="hidden" name="page" value="wp-subscription" />
49 + <input type="hidden" name="page" value="wp-subscription-list" />
40 50
41 51 <!-- Toolbar -->
42 52 <div class="wpsubs-toolbar">
43 53
@@ -52,12 +62,20 @@
52 62 'value' => 'active',
53 63 'label' => __( 'Active', 'subscription' ),
54 64 ),
55 65 array(
66 + 'value' => 'on_hold',
67 + 'label' => __( 'On Hold', 'subscription' ),
68 + ),
69 + array(
56 70 'value' => 'pending',
57 71 'label' => __( 'Pending', 'subscription' ),
58 72 ),
59 73 array(
74 + 'value' => 'pe_cancelled',
75 + 'label' => __( 'Pending Cancellation', 'subscription' ),
76 + ),
77 + array(
60 78 'value' => 'cancelled',
61 79 'label' => __( 'Cancelled', 'subscription' ),
62 80 ),
63 81 array(
@@ -64,10 +82,10 @@
64 82 'value' => 'expired',
65 83 'label' => __( 'Expired', 'subscription' ),
66 84 ),
67 85 array(
68 - 'value' => 'draft',
69 - 'label' => __( 'Draft', 'subscription' ),
86 + 'value' => 'completed',
87 + 'label' => __( 'Completed', 'subscription' ),
70 88 ),
71 89 array(
72 90 'value' => 'trash',
73 91 'label' => __( 'Trash', 'subscription' ),
@@ -94,8 +112,32 @@
94 112 )
95 113 );
96 114 ?>
97 115
116 + <?php
117 + // Next-renewal window. The Overview's "Renewals due" figure opens the
118 + // list with 7 chosen; a window reached by URL is offered too, so the
119 + // dropdown always shows what is applied.
120 + $renewal_windows = array_unique( array_merge( array( 7, 14, 30 ), $renewal_due ? array( $renewal_due ) : array() ) );
121 + sort( $renewal_windows );
122 + $renewal_options = array();
123 + foreach ( $renewal_windows as $days ) {
124 + $renewal_options[] = array(
125 + 'value' => (string) $days,
126 + /* translators: %d: number of days ahead. */
127 + 'label' => sprintf( _n( 'Due in %d day', 'Due in %d days', $days, 'subscription' ), $days ),
128 + );
129 + }
130 + wpsubs_render_adv_select(
131 + array(
132 + 'name' => 'renewal_due',
133 + 'placeholder' => __( 'All Renewals', 'subscription' ),
134 + 'value' => $renewal_due ? (string) $renewal_due : '',
135 + 'options' => $renewal_options,
136 + )
137 + );
138 + ?>
139 +
98 140 <div class="wpsubs-search">
99 141 <div class="wpsubs-input-wrap wpsubs-input-wrap--icon-l">
100 142 <svg class="wpsubs-input-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"/></svg>
101 143 <input type="search" name="s" value="<?php echo esc_attr( $search ); ?>" placeholder="<?php esc_attr_e( 'Search subscriptions...', 'subscription' ); ?>" class="wpsubs-input" />
@@ -106,9 +148,9 @@
106 148 <?php esc_html_e( 'Filter', 'subscription' ); ?>
107 149 </button>
108 150
109 151 <?php if ( $filters_active ) : ?>
110 - <a href="<?php echo esc_url( remove_query_arg( array( 'subscrpt_status', 'date_filter', 's', 'title', 'paged' ) ) ); ?>" class="wpsubs-btn wpsubs-btn--outline">
152 + <a href="<?php echo esc_url( remove_query_arg( array( 'subscrpt_status', 'date_filter', 's', 'title', 'paged', 'renewal_due' ) ) ); ?>" class="wpsubs-btn wpsubs-btn--outline">
111 153 <?php esc_html_e( 'Clear', 'subscription' ); ?>
112 154 </a>
113 155 <?php endif; ?>
114 156
@@ -113,34 +155,18 @@
113 155 <?php endif; ?>
114 156
115 157 <div class="wpsubs-toolbar__spacer"></div>
116 158
159 + <?php // Page size and bulk actions wrap as one right-aligned group, never one without the other. ?>
160 + <div style="display:flex;flex-wrap:wrap;gap:8px;margin-inline-start:auto;">
117 161 <?php
118 162 $current_per_page = isset( $_GET['per_page'] ) ? intval( wp_unslash( $_GET['per_page'] ) ) : 20;
119 - wpsubs_render_adv_select(
163 + wpsubs_render_per_page_select(
120 164 array(
121 - 'name' => 'per_page',
122 - 'value' => (string) $current_per_page,
123 - 'options' => array(
124 - array(
125 - 'value' => '10',
126 - 'label' => __( '10 / page', 'subscription' ),
127 - ),
128 - array(
129 - 'value' => '20',
130 - 'label' => __( '20 / page', 'subscription' ),
131 - ),
132 - array(
133 - 'value' => '50',
134 - 'label' => __( '50 / page', 'subscription' ),
135 - ),
136 - array(
137 - 'value' => '100',
138 - 'label' => __( '100 / page', 'subscription' ),
139 - ),
140 - ),
141 - 'align' => 'right',
142 - 'id' => 'wpsubs-per-page-select',
165 + 'name' => 'per_page',
166 + 'value' => (string) $current_per_page,
167 + 'align' => 'right',
168 + 'id' => 'wpsubs-per-page-select',
143 169 )
144 170 );
145 171 ?>
146 172
@@ -182,14 +208,15 @@
182 208 <button type="submit" name="bulk_action" value="1" id="wpsubs-bulk-action-submit" style="display:none;" aria-hidden="true"></button>
183 209
184 210 <?php
185 211 if ( 'trash' === $status && ! empty( $subscriptions ) ) :
186 - $empty_trash_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=clean_trash&sub_id=all' ), 'wpsubs_action_clean_trash' );
212 + $empty_trash_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription-list&action=clean_trash&sub_id=all' ), 'wpsubs_action_clean_trash' );
187 213 ?>
188 214 <a href="<?php echo esc_url( $empty_trash_url ); ?>" class="wpsubs-btn wpsubs-btn--danger" onclick="return confirm('<?php esc_attr_e( 'Permanently delete all trash items? This cannot be undone.', 'subscription' ); ?>')">
189 215 <?php esc_html_e( 'Empty Trash', 'subscription' ); ?>
190 216 </a>
191 217 <?php endif; ?>
218 + </div>
192 219
193 220 </div><!-- /.wpsubs-toolbar -->
194 221
195 222 <!-- Table card -->
@@ -241,20 +268,23 @@
241 268 $is_trash = 'trash' === $subscription->post_status;
242 269 $is_grace_period = isset( $subscription_data['grace_period'] );
243 270 $grace_remaining = $subscription_data['grace_period']['remaining_days'] ?? 0;
244 271
245 - // Amount + timing
246 - $quantity = $order_item ? (int) $order_item->get_quantity() : 1;
247 - $price = '' !== ( $subscription_data['price'] ?? '' )
248 - ? (float) $subscription_data['price'] * max( 1, $quantity )
249 - : '';
250 - $timing_per = $subscription_data['schedule']['timing_per'] ?? '';
251 - $timing_option = $subscription_data['schedule']['timing_option'] ?? '';
252 - $timing_label = '';
272 + // Amount + timing. Net of any discount that carries into renewals.
273 + $quantity = $order_item ? (int) $order_item->get_quantity() : 1;
274 + $amount_totals = SpringDevs\Subscription\Illuminate\Helper::get_subscription_display_totals( $subscription_id, $order_item );
275 + $price = '' !== ( $subscription_data['price'] ?? '' ) ? (float) $amount_totals['total'] : '';
276 + $price_full = (float) $amount_totals['full_excl'] + $amount_totals['tax'] + $amount_totals['discount_tax'];
277 + $has_discount = (bool) $amount_totals['has_discount'];
278 + $price_per_unit = '' !== $price ? $price / max( 1, $quantity ) : '';
279 + $timing_per = $subscription_data['schedule']['timing_per'] ?? '';
280 + $timing_option = $subscription_data['schedule']['timing_option'] ?? '';
281 + $timing_label = '';
253 282 if ( $timing_per && $timing_option ) {
283 + $timing_unit = \SpringDevs\Subscription\Illuminate\Helper::get_typos( $timing_per, $timing_option );
254 284 $timing_label = ( (int) $timing_per > 1 )
255 - ? $timing_per . ' ' . $timing_option . 's'
256 - : $timing_option;
285 + ? $timing_per . ' ' . $timing_unit
286 + : $timing_unit;
257 287 }
258 288
259 289 // Avatar
260 290 $name_parts = array_values( array_filter( explode( ' ', trim( $customer ) ) ) );
@@ -268,12 +298,12 @@
268 298 $color_slot = ord( strtolower( $initials[0] ?? 'a' ) ) % 8;
269 299
270 300 // Action URLs
271 301 $nonce_action = 'wpsubs_action_' . $subscription->ID;
272 - $view_url = get_edit_post_link( $subscription->ID );
273 - $trash_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=trash&sub_id=' . $subscription->ID ), $nonce_action );
274 - $delete_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=delete&sub_id=' . $subscription->ID ), $nonce_action );
275 - $restore_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=restore&sub_id=' . $subscription->ID ), $nonce_action );
302 + $view_url = admin_url( 'admin.php?page=wp-subscription-details&id=' . $subscription->ID );
303 + $trash_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription-list&action=trash&sub_id=' . $subscription->ID ), $nonce_action );
304 + $delete_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription-list&action=delete&sub_id=' . $subscription->ID ), $nonce_action );
305 + $restore_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription-list&action=restore&sub_id=' . $subscription->ID ), $nonce_action );
276 306
277 307 // Status badge
278 308 $badge_mod_map = array(
279 309 'active' => 'active',
@@ -280,8 +310,9 @@
280 310 'pending' => 'pending',
281 311 'pe_cancelled' => 'pending-cancel',
282 312 'cancelled' => 'cancelled',
283 313 'expired' => 'expired',
314 + 'completed' => 'completed',
284 315 'draft' => 'draft',
285 316 'trash' => 'trash',
286 317 );
287 318 $badge_mod = $badge_mod_map[ $subscrpt_status ] ?? 'expired';
@@ -302,9 +333,9 @@
302 333 <td style="white-space:nowrap;">
303 334 <span class="wpsubs-badge wpsubs-badge--<?php echo esc_attr( $badge_mod ); ?>">
304 335 <?php echo esc_html( $verbose_status ); ?>
305 336 <?php if ( $is_grace_period && $grace_remaining > 0 ) : ?>
306 - <span class="dashicons dashicons-warning" style="font-size:11px;width:11px;height:11px;color:#d97706;" title="<?php echo esc_attr( sprintf( __( '%d days remaining in grace period', 'subscription' ), $grace_remaining ) ); ?>"></span>
337 + <span class="dashicons dashicons-warning" style="font-size:11px;width:11px;height:11px;color:#d97706;" title="<?php echo esc_attr( sprintf( /* translators: %d: number of days left in the grace period. */ __( '%d days remaining in grace period', 'subscription' ), $grace_remaining ) ); ?>"></span>
307 338 <?php endif; ?>
308 339 </span>
309 340 </td>
310 341
@@ -336,10 +367,13 @@
336 367 <!-- Subscription amount + timing -->
337 368 <td style="white-space:nowrap;">
338 369 <?php if ( '' !== $price ) : ?>
339 370 <span class="wpsubs-cell-title" style="font-variant-numeric:tabular-nums;"><?php echo wp_kses_post( wc_price( $price ) ); ?></span>
371 + <?php if ( $has_discount ) : ?>
372 + <span class="wpsubs-cell-id"><del aria-hidden="true"><?php echo wp_kses_post( wc_price( $price_full ) ); ?></del></span>
373 + <?php endif; ?>
340 374 <?php if ( $quantity > 1 ) : ?>
341 - <span class="wpsubs-cell-id"><?php echo wp_kses_post( wc_price( (float) $subscription_data['price'] ) ); ?> &times; <?php echo (int) $quantity; ?></span>
375 + <span class="wpsubs-cell-id"><?php echo wp_kses_post( wc_price( (float) $price_per_unit ) ); ?> &times; <?php echo (int) $quantity; ?></span>
342 376 <?php endif; ?>
343 377 <?php if ( $timing_label ) : ?>
344 378 <span class="wpsubs-cell-id">/ <?php echo esc_html( $timing_label ); ?></span>
345 379 <?php endif; ?>
@@ -403,114 +437,23 @@
403 437 </table>
404 438
405 439 <!-- Pagination inside table card -->
406 440 <?php
407 - $start_item = $total > 0 ? ( ( $paged - 1 ) * $per_page ) + 1 : 0;
408 - $end_item = min( $paged * $per_page, $total );
409 - $base_url = remove_query_arg( 'paged' );
410 - $total_pages = max( 1, $max_num_pages );
411 -
412 - // Build page range: first, last, current ± 1 neighbours, ellipsis for gaps.
413 - $nearby = array();
414 - for ( $i = 1; $i <= $total_pages; $i++ ) {
415 - if ( $i === 1 || $i === $total_pages || abs( $i - $paged ) <= 1 ) {
416 - $nearby[] = $i;
417 - }
418 - }
419 - $page_range = array();
420 - $prev_page = null;
421 - foreach ( $nearby as $p ) {
422 - if ( null !== $prev_page ) {
423 - $gap = $p - $prev_page;
424 - if ( $gap === 2 ) {
425 - $page_range[] = $prev_page + 1; // single hidden page — show it directly
426 - } elseif ( $gap > 2 ) {
427 - $page_range[] = null; // ellipsis
428 - }
429 - }
430 - $page_range[] = $p;
431 - $prev_page = $p;
432 - }
441 + wpsubs_render_pager(
442 + array(
443 + 'current' => (int) $paged,
444 + 'total' => max( 1, (int) $max_num_pages ),
445 + 'info' => true,
446 + 'per_page' => (int) $per_page,
447 + 'item_count' => (int) $total,
448 + 'base_url' => remove_query_arg( 'paged' ),
449 + 'link_mode' => 'url',
450 + // translators: %1$s = first item number, %2$s = last item number, %3$s = total items.
451 + 'info_format' => __( 'Showing %1$s–%2$s of %3$s subscriptions', 'subscription' ),
452 + 'context' => 'subscription-list',
453 + )
454 + );
433 455 ?>
434 - <div class="wpsubs-pagination">
435 - <span class="wpsubs-pagination__info">
436 - <?php
437 - echo esc_html(
438 - sprintf(
439 - /* translators: 1: first item number, 2: last item number, 3: total count */
440 - __( 'Showing %1$s–%2$s of %3$s subscriptions', 'subscription' ),
441 - number_format_i18n( $start_item ),
442 - number_format_i18n( $end_item ),
443 - number_format_i18n( $total )
444 - )
445 - );
446 - ?>
447 - </span>
448 - <div class="wpsubs-pagination__nav">
449 -
450 - <?php // Previous button ?>
451 - <?php if ( $paged > 1 ) : ?>
452 - <a href="
453 - <?php
454 - echo esc_url(
455 - add_query_arg(
456 - array(
457 - 'paged' => $paged - 1,
458 - 'per_page' => $per_page,
459 - ),
460 - $base_url
461 - )
462 - );
463 - ?>
464 - " class="wpsubs-pagination__btn" aria-label="<?php esc_attr_e( 'Previous page', 'subscription' ); ?>">&#8249;</a>
465 - <?php else : ?>
466 - <span class="wpsubs-pagination__btn wpsubs-pagination__btn--disabled" aria-hidden="true">&#8249;</span>
467 - <?php endif; ?>
468 -
469 - <?php // Numbered pages + ellipsis ?>
470 - <?php foreach ( $page_range as $p ) : ?>
471 - <?php if ( null === $p ) : ?>
472 - <span class="wpsubs-pagination__ellipsis" aria-hidden="true">…</span>
473 - <?php elseif ( $p === $paged ) : ?>
474 - <span class="wpsubs-pagination__btn wpsubs-pagination__btn--active" aria-current="page"><?php echo (int) $p; ?></span>
475 - <?php else : ?>
476 - <a href="
477 - <?php
478 - echo esc_url(
479 - add_query_arg(
480 - array(
481 - 'paged' => $p,
482 - 'per_page' => $per_page,
483 - ),
484 - $base_url
485 - )
486 - );
487 - ?>
488 - " class="wpsubs-pagination__btn"><?php echo (int) $p; ?></a>
489 - <?php endif; ?>
490 - <?php endforeach; ?>
491 -
492 - <?php // Next button ?>
493 - <?php if ( $paged < $total_pages ) : ?>
494 - <a href="
495 - <?php
496 - echo esc_url(
497 - add_query_arg(
498 - array(
499 - 'paged' => $paged + 1,
500 - 'per_page' => $per_page,
501 - ),
502 - $base_url
503 - )
504 - );
505 - ?>
506 - " class="wpsubs-pagination__btn" aria-label="<?php esc_attr_e( 'Next page', 'subscription' ); ?>">&#8250;</a>
507 - <?php else : ?>
508 - <span class="wpsubs-pagination__btn wpsubs-pagination__btn--disabled" aria-hidden="true">&#8250;</span>
509 - <?php endif; ?>
510 -
511 - </div>
512 - </div>
513 456
514 457 </div><!-- /.wpsubs-table-card -->
515 458
516 459 </form>