PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 1.10.7
Subscriptions for WooCommerce with Stripe Recurring Payments v1.10.7
2.4.0 2.3.0 2.2.0 2.1.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 All 65 releases
subscription / includes / Admin / views / subscription-list.php

subscription-list.php in Subscriptions for WooCommerce with Stripe Recurring Payments 1.10.7, at includes/Admin/views/subscription-list.php

524 lines 19.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Subscription admin list view.
4 *
5 * @package SpringDevs\Subscription\Admin
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 if ( ! isset( $date_filter ) ) {
13 $date_filter = '';
14 }
15
16 $filters_active = ! empty( $status ) || ! empty( $date_filter ) || ! empty( $search );
17 $months = array();
18 for ( $i = 0; $i < 12; $i++ ) {
19 $month = strtotime( "-$i month" );
20 $months[ date( 'Y-m', $month ) ] = date( 'F Y', $month );
21 }
22 ?>
23 <div class="wp-subscription-admin-content list-page subscrpt-subs-list">
24
25 <?php
26 // Getting started card — hidden once a subscription product exists.
27 require __DIR__ . '/subscription-gsc.php';
28 ?>
29
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>
36
37 <form method="post" id="subscriptions-form">
38 <?php wp_nonce_field( 'subscrpt_list_action' ); ?>
39 <input type="hidden" name="page" value="wp-subscription" />
40
41 <!-- Toolbar -->
42 <div class="wpsubs-toolbar">
43
44 <?php
45 wpsubs_render_adv_select(
46 array(
47 'name' => 'subscrpt_status',
48 'placeholder' => __( 'All Status', 'subscription' ),
49 'value' => $status,
50 'options' => array(
51 array(
52 'value' => 'active',
53 'label' => __( 'Active', 'subscription' ),
54 ),
55 array(
56 'value' => 'pending',
57 'label' => __( 'Pending', 'subscription' ),
58 ),
59 array(
60 'value' => 'cancelled',
61 'label' => __( 'Cancelled', 'subscription' ),
62 ),
63 array(
64 'value' => 'expired',
65 'label' => __( 'Expired', 'subscription' ),
66 ),
67 array(
68 'value' => 'draft',
69 'label' => __( 'Draft', 'subscription' ),
70 ),
71 array(
72 'value' => 'trash',
73 'label' => __( 'Trash', 'subscription' ),
74 ),
75 ),
76 )
77 );
78 ?>
79
80 <?php
81 $date_options = array();
82 foreach ( $months as $val => $label ) {
83 $date_options[] = array(
84 'value' => $val,
85 'label' => $label,
86 );
87 }
88 wpsubs_render_adv_select(
89 array(
90 'name' => 'date_filter',
91 'placeholder' => __( 'All Dates', 'subscription' ),
92 'value' => $date_filter,
93 'options' => $date_options,
94 )
95 );
96 ?>
97
98 <div class="wpsubs-search">
99 <div class="wpsubs-input-wrap wpsubs-input-wrap--icon-l">
100 <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 <input type="search" name="s" value="<?php echo esc_attr( $search ); ?>" placeholder="<?php esc_attr_e( 'Search subscriptions...', 'subscription' ); ?>" class="wpsubs-input" />
102 </div>
103 </div>
104
105 <button type="submit" name="filter_action" value="filter" class="wpsubs-btn wpsubs-btn--outline">
106 <?php esc_html_e( 'Filter', 'subscription' ); ?>
107 </button>
108
109 <?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">
111 <?php esc_html_e( 'Clear', 'subscription' ); ?>
112 </a>
113 <?php endif; ?>
114
115 <div class="wpsubs-toolbar__spacer"></div>
116
117 <?php
118 $current_per_page = isset( $_GET['per_page'] ) ? intval( wp_unslash( $_GET['per_page'] ) ) : 20;
119 wpsubs_render_adv_select(
120 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',
143 )
144 );
145 ?>
146
147 <?php
148 // Bulk action advanced-select + hidden submit.
149 $bulk_options = 'trash' === $status
150 ? array(
151 array(
152 'value' => 'restore',
153 'label' => __( 'Restore selected', 'subscription' ),
154 ),
155 array(
156 'value' => 'delete',
157 'label' => __( 'Delete permanently', 'subscription' ),
158 'danger' => true,
159 'confirm' => __( 'Delete selected subscriptions permanently? This cannot be undone.', 'subscription' ),
160 ),
161 )
162 : array(
163 array(
164 'value' => 'trash',
165 'label' => __( 'Move to trash', 'subscription' ),
166 'danger' => true,
167 'confirm' => __( 'Move selected subscriptions to trash?', 'subscription' ),
168 ),
169 );
170
171 wpsubs_render_adv_select(
172 array(
173 'name' => 'action',
174 'placeholder' => __( 'Select Action', 'subscription' ),
175 'value' => '-1',
176 'options' => $bulk_options,
177 'id' => 'wpsubs-bulk-action-select',
178 'align' => 'right',
179 )
180 );
181 ?>
182 <button type="submit" name="bulk_action" value="1" id="wpsubs-bulk-action-submit" style="display:none;" aria-hidden="true"></button>
183
184 <?php
185 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' );
187 ?>
188 <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 <?php esc_html_e( 'Empty Trash', 'subscription' ); ?>
190 </a>
191 <?php endif; ?>
192
193 </div><!-- /.wpsubs-toolbar -->
194
195 <!-- Table card -->
196 <h2 class="screen-reader-text"><?php esc_html_e( 'Subscriptions list', 'subscription' ); ?></h2>
197 <div class="wpsubs-table-card">
198 <table class="wpsubs-table">
199 <thead>
200 <tr>
201 <th class="wpsubs-col--check">
202 <input type="checkbox" id="cb-select-all" class="wpsubs-checkbox">
203 </th>
204 <th style="width:1%;white-space:nowrap;"><?php esc_html_e( 'Status', 'subscription' ); ?></th>
205 <th><?php esc_html_e( 'Subscription', 'subscription' ); ?></th>
206 <th><?php esc_html_e( 'Customer', 'subscription' ); ?></th>
207 <th><?php esc_html_e( 'Amount', 'subscription' ); ?></th>
208 <th><?php esc_html_e( 'Started', 'subscription' ); ?></th>
209 <th><?php esc_html_e( 'Next Renewal', 'subscription' ); ?></th>
210 <th class="wpsubs-col--actions"><?php esc_html_e( 'Actions', 'subscription' ); ?></th>
211 </tr>
212 </thead>
213 <tbody>
214
215 <?php if ( ! empty( $subscriptions ) ) : ?>
216 <?php
217 foreach ( $subscriptions as $subscription ) :
218 $subscription_id = $subscription->ID;
219 $subscription_data = SpringDevs\Subscription\Illuminate\Helper::get_subscription_data( $subscription_id );
220
221 $subscrpt_status = $subscription_data['status'] ?? '';
222 $subscrpt_status = empty( $subscrpt_status ) ? get_post_status( $subscription_id ) : $subscrpt_status;
223
224 $order_id = $subscription_data['order']['order_id'] ?? 0;
225 $order_item_id = $subscription_data['order']['order_item_id'] ?? 0;
226 $order = $order_id ? wc_get_order( $order_id ) : null;
227 $order_item = $order ? $order->get_item( $order_item_id ) : null;
228 $product_name = $order_item ? $order_item->get_name() : '-';
229
230 $product_id = $subscription_data['product']['variation_id'] ?: ( $subscription_data['product']['product_id'] ?? 0 );
231 $product_url = $product_id ? get_edit_post_link( $product_id ) : '';
232
233 $customer = $order ? $order->get_formatted_billing_full_name() : '-';
234 $customer_id = $order ? $order->get_customer_id() : 0;
235 $customer_url = $customer_id ? admin_url( 'user-edit.php?user_id=' . $customer_id ) : '';
236 $customer_email = $order ? $order->get_billing_email() : '';
237
238 $start_date = $subscription_data['start_date'] ? strtotime( $subscription_data['start_date'] ) : 0;
239 $renewal_date = $subscription_data['next_date'] ? strtotime( $subscription_data['next_date'] ) : 0;
240
241 $is_trash = 'trash' === $subscription->post_status;
242 $is_grace_period = isset( $subscription_data['grace_period'] );
243 $grace_remaining = $subscription_data['grace_period']['remaining_days'] ?? 0;
244
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 = '';
253 if ( $timing_per && $timing_option ) {
254 $timing_label = ( (int) $timing_per > 1 )
255 ? $timing_per . ' ' . $timing_option . 's'
256 : $timing_option;
257 }
258
259 // Avatar
260 $name_parts = array_values( array_filter( explode( ' ', trim( $customer ) ) ) );
261 $initials = '?';
262 if ( $name_parts ) {
263 $initials = '';
264 foreach ( array_slice( $name_parts, 0, 2 ) as $part ) {
265 $initials .= strtoupper( $part[0] );
266 }
267 }
268 $color_slot = ord( strtolower( $initials[0] ?? 'a' ) ) % 8;
269
270 // Action URLs
271 $nonce_action = 'wpsubs_action_' . $subscription->ID;
272 $view_url = admin_url( 'admin.php?page=wp-subscription-details&id=' . $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 );
276
277 // Status badge
278 $badge_mod_map = array(
279 'active' => 'active',
280 'pending' => 'pending',
281 'pe_cancelled' => 'pending-cancel',
282 'cancelled' => 'cancelled',
283 'expired' => 'expired',
284 'draft' => 'draft',
285 'trash' => 'trash',
286 );
287 $badge_mod = $badge_mod_map[ $subscrpt_status ] ?? 'expired';
288 $verbose_status = SpringDevs\Subscription\Illuminate\Helper::get_verbose_status( $subscrpt_status );
289
290 if ( $is_grace_period && $grace_remaining > 0 ) {
291 $badge_mod = 'active';
292 $verbose_status = __( 'Active', 'subscription' );
293 }
294 ?>
295 <tr>
296 <!-- Checkbox -->
297 <td class="wpsubs-col--check">
298 <input type="checkbox" name="subscription_ids[]" value="<?php echo esc_attr( $subscription->ID ); ?>" class="wpsubs-checkbox wpsubs-row-check">
299 </td>
300
301 <!-- Status badge -->
302 <td style="white-space:nowrap;">
303 <span class="wpsubs-badge wpsubs-badge--<?php echo esc_attr( $badge_mod ); ?>">
304 <?php echo esc_html( $verbose_status ); ?>
305 <?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>
307 <?php endif; ?>
308 </span>
309 </td>
310
311 <!-- Subscription: product name + subscription ID -->
312 <td>
313 <a href="<?php echo esc_url( $view_url ); ?>" class="wpsubs-cell-title" style="font-weight:600;"><?php echo esc_html( $product_name ); ?></a>
314 <span class="wpsubs-cell-id">#<?php echo (int) $subscription->ID; ?></span>
315 </td>
316
317 <!-- Customer avatar + name + email -->
318 <td>
319 <div class="wpsubs-customer">
320 <div class="wpsubs-avatar" data-color="<?php echo (int) $color_slot; ?>">
321 <?php echo esc_html( $initials ); ?>
322 </div>
323 <div class="wpsubs-customer__info">
324 <?php if ( $customer_url ) : ?>
325 <a href="<?php echo esc_url( $customer_url ); ?>" class="wpsubs-customer__name"><?php echo esc_html( $customer ); ?></a>
326 <?php else : ?>
327 <span class="wpsubs-customer__name"><?php echo esc_html( $customer ); ?></span>
328 <?php endif; ?>
329 <?php if ( $customer_email ) : ?>
330 <span class="wpsubs-customer__sub"><?php echo esc_html( $customer_email ); ?></span>
331 <?php endif; ?>
332 </div>
333 </div>
334 </td>
335
336 <!-- Subscription amount + timing -->
337 <td style="white-space:nowrap;">
338 <?php if ( '' !== $price ) : ?>
339 <span class="wpsubs-cell-title" style="font-variant-numeric:tabular-nums;"><?php echo wp_kses_post( wc_price( $price ) ); ?></span>
340 <?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>
342 <?php endif; ?>
343 <?php if ( $timing_label ) : ?>
344 <span class="wpsubs-cell-id">/ <?php echo esc_html( $timing_label ); ?></span>
345 <?php endif; ?>
346 <?php else : ?>
347 <span class="wpsubs-cell--muted">&#8212;</span>
348 <?php endif; ?>
349 </td>
350
351 <!-- Start date -->
352 <td class="wpsubs-col--nowrap">
353 <?php if ( $start_date ) : ?>
354 <span class="wpsubs-cell-title"><?php echo esc_html( wp_date( get_option( 'date_format' ), $start_date ) ); ?></span>
355 <span class="wpsubs-cell-id"><?php echo esc_html( wp_date( get_option( 'time_format' ), $start_date ) ); ?></span>
356 <?php else : ?>
357 <span class="wpsubs-cell--muted">&#8212;</span>
358 <?php endif; ?>
359 </td>
360
361 <!-- Renewal date -->
362 <td class="wpsubs-col--nowrap">
363 <?php if ( $renewal_date ) : ?>
364 <span class="wpsubs-cell-title"><?php echo esc_html( wp_date( get_option( 'date_format' ), $renewal_date ) ); ?></span>
365 <span class="wpsubs-cell-id"><?php echo esc_html( wp_date( get_option( 'time_format' ), $renewal_date ) ); ?></span>
366 <?php else : ?>
367 <span class="wpsubs-cell--muted">&#8212;</span>
368 <?php endif; ?>
369 </td>
370
371 <!-- Actions -->
372 <td class="wpsubs-cell--actions">
373 <div class="wpsubs-row-actions">
374 <button type="button" class="wpsubs-row-actions__trigger" aria-label="<?php esc_attr_e( 'Row actions', 'subscription' ); ?>">···</button>
375 <div class="wpsubs-dropdown">
376 <a href="<?php echo esc_url( $view_url ); ?>" class="wpsubs-dropdown__item"><?php esc_html_e( 'View / Edit', 'subscription' ); ?></a>
377 <div class="wpsubs-dropdown__divider"></div>
378 <?php if ( ! $is_trash ) : ?>
379 <a href="<?php echo esc_url( $trash_url ); ?>" class="wpsubs-dropdown__item wpsubs-dropdown__item--danger" onclick="return confirm('<?php esc_attr_e( 'Move this subscription to trash?', 'subscription' ); ?>')"><?php esc_html_e( 'Trash', 'subscription' ); ?></a>
380 <?php else : ?>
381 <a href="<?php echo esc_url( $restore_url ); ?>" class="wpsubs-dropdown__item"><?php esc_html_e( 'Restore', 'subscription' ); ?></a>
382 <a href="<?php echo esc_url( $delete_url ); ?>" class="wpsubs-dropdown__item wpsubs-dropdown__item--danger" onclick="return confirm('<?php esc_attr_e( 'Delete permanently? This cannot be undone.', 'subscription' ); ?>')"><?php esc_html_e( 'Delete Permanently', 'subscription' ); ?></a>
383 <?php endif; ?>
384 </div>
385 </div>
386 </td>
387 </tr>
388 <?php endforeach; ?>
389
390 <?php else : ?>
391 <tr>
392 <td colspan="8">
393 <div class="wpsubs-empty">
394 <div class="wpsubs-empty__icon">📋</div>
395 <div class="wpsubs-empty__title"><?php esc_html_e( 'No subscriptions found', 'subscription' ); ?></div>
396 <div class="wpsubs-empty__desc"><?php esc_html_e( 'Subscriptions will appear here once customers complete checkout.', 'subscription' ); ?></div>
397 </div>
398 </td>
399 </tr>
400 <?php endif; ?>
401
402 </tbody>
403 </table>
404
405 <!-- Pagination inside table card -->
406 <?php
407 wpsubs_render_pager(
408 array(
409 'current' => (int) $paged,
410 'total' => max( 1, (int) $max_num_pages ),
411 'info' => true,
412 'per_page' => (int) $per_page,
413 'item_count' => (int) $total,
414 'base_url' => remove_query_arg( 'paged' ),
415 'link_mode' => 'url',
416 // translators: %1$s = first item number, %2$s = last item number, %3$s = total items.
417 'info_format' => __( 'Showing %1$s–%2$s of %3$s subscriptions', 'subscription' ),
418 'context' => 'subscription-list',
419 )
420 );
421 ?>
422
423 </div><!-- /.wpsubs-table-card -->
424
425 </form>
426
427 </div>
428
429 <script>
430 ( function () {
431 var selectAll = document.getElementById( 'cb-select-all' );
432 var form = document.getElementById( 'subscriptions-form' );
433
434 function rowChecks() {
435 return form ? Array.from( form.querySelectorAll( '.wpsubs-row-check' ) ) : [];
436 }
437
438 function syncSelectAll() {
439 if ( ! selectAll ) return;
440 var checks = rowChecks();
441 var checked = checks.filter( function ( cb ) { return cb.checked; } );
442 checks.forEach( function ( cb ) {
443 var row = cb.closest( 'tr' );
444 if ( row ) row.classList.toggle( 'wpsubs-row--selected', cb.checked );
445 } );
446 selectAll.indeterminate = checked.length > 0 && checked.length < checks.length;
447 selectAll.checked = checks.length > 0 && checked.length === checks.length;
448 }
449
450 if ( selectAll ) {
451 selectAll.addEventListener( 'change', function () {
452 rowChecks().forEach( function ( cb ) { cb.checked = selectAll.checked; } );
453 syncSelectAll();
454 } );
455 }
456
457 if ( form ) {
458 form.addEventListener( 'change', function ( e ) {
459 if ( e.target.classList.contains( 'wpsubs-row-check' ) ) syncSelectAll();
460 } );
461 }
462
463 // ── Advanced-select event handlers ──────────────────────────
464 var bulkSubmit = document.getElementById( 'wpsubs-bulk-action-submit' );
465 document.addEventListener( 'wpsubs:select', function ( e ) {
466 var id = e.target && e.target.id;
467
468 // Bulk action: submit via hidden button
469 if ( id === 'wpsubs-bulk-action-select' && bulkSubmit ) {
470 bulkSubmit.click();
471 return;
472 }
473
474 // Per-page: auto-submit as a filter action
475 if ( id === 'wpsubs-per-page-select' && form ) {
476 var fi = document.createElement( 'input' );
477 fi.type = 'hidden';
478 fi.name = 'filter_action';
479 fi.value = 'filter';
480 form.appendChild( fi );
481 form.submit();
482 }
483 } );
484
485 // ── Row action dropdowns ─────────────────────────────────────
486 function closeAllRowMenus() {
487 document.querySelectorAll( '.wpsubs-row-actions--open' ).forEach( function ( el ) {
488 el.classList.remove( 'wpsubs-row-actions--open' );
489 var d = el.querySelector( '.wpsubs-dropdown' );
490 if ( d ) d.classList.remove( 'wpsubs-dropdown--open' );
491 } );
492 }
493
494 document.addEventListener( 'click', function ( e ) {
495 var trigger = e.target.closest( '.wpsubs-row-actions__trigger' );
496
497 document.querySelectorAll( '.wpsubs-row-actions--open' ).forEach( function ( el ) {
498 if ( el !== ( trigger && trigger.closest( '.wpsubs-row-actions' ) ) ) {
499 el.classList.remove( 'wpsubs-row-actions--open' );
500 var d = el.querySelector( '.wpsubs-dropdown' );
501 if ( d ) d.classList.remove( 'wpsubs-dropdown--open' );
502 }
503 } );
504
505 if ( ! trigger ) return;
506
507 e.stopPropagation();
508 var wrapper = trigger.closest( '.wpsubs-row-actions' );
509 var dropdown = wrapper && wrapper.querySelector( '.wpsubs-dropdown' );
510 if ( ! wrapper || ! dropdown ) return;
511
512 var isOpen = wrapper.classList.contains( 'wpsubs-row-actions--open' );
513 wrapper.classList.toggle( 'wpsubs-row-actions--open', ! isOpen );
514 dropdown.classList.toggle( 'wpsubs-dropdown--open', ! isOpen );
515 } );
516
517 document.addEventListener( 'keydown', function ( e ) {
518 if ( e.key === 'Escape' ) closeAllRowMenus();
519 } );
520
521 syncSelectAll();
522 }() );
523 </script>
524